using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Microsoft.Ajax.Utilities;
using Nop.Admin.Models.Common;
using Nop.Admin.Models.Customers;
using Nop.Admin.Models.Logistics;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Logistics;
using Nop.Services.Orders;
using Nop.Services.Security;
using Nop.Services.Shipping;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.Extensions;
using Nop.Services.Messages;

namespace Nop.Admin.Controllers
{
    [AdminAuthorize]
    public partial class LogisticsController : BaseNopController
    {
        private readonly ILogisticsService _logisticsService;
        private readonly ILogisticsProcessingService _logisticsProcessingService;
        private readonly IPermissionService _permissionService;
        private readonly ILocalizationService _localizationService;
        private readonly IPdfService _pdfService;
        private readonly IWorkContext _workContext;
        private readonly IOrderService _orderService;
        private readonly IAddressService _addressService;
        private readonly ICountryService _countryService;
        private readonly IStateProvinceService _stateProvinceService;
        private readonly AddressSettings _addressSettings;
        private readonly ICustomerActivityService _customerActivityService;
        private readonly ICustomerService _customerService;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly ICustomerRegistrationService _customerRegistrationService;
        private readonly CustomerSettings _customerSettings;
        private readonly AdminAreaSettings _adminAreaSettings;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly IMeasureService _measureService;
        private readonly MeasureSettings _measureSettings;
        private readonly LogisticsInformationSettings _logisticsInformationSettings;
        private readonly Nop.Services.Orders.IOrderProcessingService _orderProcessingService;
        private readonly ILanguageService _languageService;
        private readonly ILocalizedEntityService _localizedEntityService;
        private readonly IRegionService _regionService;
        private readonly IWorkflowMessageService _workflowMessageService;
        private readonly IShipmentService _shipmentService;

        public LogisticsController(
            ILanguageService languageService,
            ILocalizedEntityService localizedEntityService,
            ILogisticsService logisticsService,
            ILogisticsProcessingService logisticsProcessingService,
            IPermissionService permissionService,
            ILocalizationService localizationService,
            IPdfService pdfService,
            IWorkContext workContext,
            IOrderService orderService,
            IAddressService addressService,
            ICountryService countryService,
            IStateProvinceService stateProvinceService,
            ICustomerActivityService customerActivityService,
            ICustomerService customerService,
            IGenericAttributeService genericAttributeService,
            ICustomerRegistrationService customerRegistrationService,
            CustomerSettings customerSettings,
            AddressSettings addressSettings,
            AdminAreaSettings adminAreaSettings,
            IDateTimeHelper dateTimeHelper,
            IMeasureService measureService,
            MeasureSettings measureSettings,
            LogisticsInformationSettings logisticsInformationSettings,
            Nop.Services.Orders.IOrderProcessingService orderProcessingService,
            IRegionService regionService, IWorkflowMessageService workflowMessageService,
            IShipmentService shipmentService)
        {
            this._languageService = languageService;
            this._localizedEntityService = localizedEntityService;
            this._logisticsService = logisticsService;
            this._logisticsProcessingService = logisticsProcessingService;
            this._permissionService = permissionService;
            this._localizationService = localizationService;
            this._pdfService = pdfService;
            this._workContext = workContext;
            this._orderService = orderService;
            this._addressService = addressService;
            this._customerActivityService = customerActivityService;
            this._addressSettings = addressSettings;
            this._countryService = countryService;
            this._stateProvinceService = stateProvinceService;
            this._customerService = customerService;
            this._genericAttributeService = genericAttributeService;
            this._customerRegistrationService = customerRegistrationService;
            this._customerSettings = customerSettings;
            this._adminAreaSettings = adminAreaSettings;
            this._dateTimeHelper = dateTimeHelper;
            this._measureService = measureService;
            this._measureSettings = measureSettings;
            this._logisticsInformationSettings = logisticsInformationSettings;
            this._orderProcessingService = orderProcessingService;
            this._regionService = regionService;
            this._workflowMessageService = workflowMessageService;
            this._shipmentService = shipmentService;
        }

        #region Methods

        public ActionResult Index()
        {
            return Redirect("PlaningOverview");
            //if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
            //    return AccessDeniedView();

            //var model = new LogisticsModel();
            //model.Couriers = _logisticsService.GetPlannedCouriers();
            //model.Packages = _logisticsService.GetPendingPlanPackages();
            //model.MapContext = new MapContextModel()
            //{
            //    SequentialLocations = PrepareLocationsFromPendingPackages(),
            //    ShowRoute = false
            //};

            //if (!String.IsNullOrEmpty(_logisticsInformationSettings.BingMapKey))
            //{
            //    model.MapContext.MapApiKey = _logisticsInformationSettings.BingMapKey;
            //}

            //return View(model);
        }

        public ActionResult PlanningOverview()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            var model = new PlanningOverviewModel();
            try
            {
                //build Time Span filter select
                BuildTimeSpanSelect(model);

                model.DistrictId = districtId.Value;
                model.DistrictName = _localizationService.GetResource("Admin.Logistics.Common.Fields.AllDistricts");
                if (district != null)
                {
                    model.DistrictName = district.Name;
                }

                if (districtId.Value == 0)
                {
                    var selectedDistrictId = 0;
                    var distId = HttpContext.Request.QueryString["districtId"];
                    if (!string.IsNullOrEmpty(distId))
                    {
                        int.TryParse(distId, out selectedDistrictId);
                    }

                    var allDistricts = _logisticsService.GetAllDistricts();
                    model.AvailableDistricts = allDistricts.Select(d => new SelectListItem
                                                    {
                                                        Selected = d.Id == selectedDistrictId,
                                                        Text = d.Name,
                                                        Value = d.Id.ToString()
                                                    }).ToList();
                    model.AvailableDistricts.Insert(0, new SelectListItem { Text = "All Districts", Value = "0", Selected = model.DistrictId == 0 && selectedDistrictId == 0 });

                    if (selectedDistrictId > 0)
                    {
                        district = _logisticsService.GetDistrictById(selectedDistrictId);
                    }

                    model.SelectedDistrictId = district == null ? 0 : district.Id;
                    model.DistrictId = selectedDistrictId;
                    model.DistrictName = district == null ? "All Districts" : district.Name;
                }
                else
                {
                    model.AvailableDistricts = new List<SelectListItem> 
						{ 
							new SelectListItem { Selected = true, Text = model.DistrictName, Value = model.DistrictId.ToString() } 
						};
                }

                // Warehouses
                var warehouses = _logisticsService.GetAllWarehouses(district);

                model.AvailableWarehouses.Add(new SelectListItem()
                {
                    Value = "0",
                    Text = "Warehouse: All"
                });

                model.AvailableWarehouses.AddRange(warehouses.Select(w =>
                    new SelectListItem()
                    {
                        Value = w.Id.ToString(),
                        Text = String.Format("Warehouse: {0}", w.Address.FormattedAddress()),
                    }
                ).ToList());

                if (warehouses.Count == 1)
                {
                    model.AvailableWarehouses.First(w => w.Value.Equals(warehouses.First().Id.ToString())).Selected = true;
                    model.WarehouseId = warehouses.Single().Id;
                }

                model.DisplayNotReady = true;
                model.InWarehouseOnly = false;
                model.IncludeMaxDeliveryAttempt = false;

                int[] timeSpans = new int[] { };

                var pkgs = _logisticsService.GetPendingPlanPackages(null, district, timeSpans, true);

                if (pkgs != null && pkgs.Count > 0)
                {
                    var countryCode = pkgs.First().PickUpLocation.Address.Country.TwoLetterIsoCode;
                    var allZones = _logisticsService.GetAllZones(countryCode);

                    var areas = from pck in pkgs
                                from z in allZones
                                where (_logisticsService.PackageInWarehouse(pck) && z.IsZipPostalCodeInZone(pck.DeliveryLocation.Address.ZipPostalCode)) ||
                                    (_logisticsService.PackageAtPickupLocation(pck) && z.IsZipPostalCodeInZone(pck.PickUpLocation.Address.ZipPostalCode))
                                select z;
                    model.AvailableLogisticAreas =
                        areas.Distinct()
                        .OrderBy(x => x.Name)
                        .Select(x => new SelectListItem() { Text = x.Name, Value = x.Id.ToString() })
                        .ToList();

                    model.AvailableLogisticAreas.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true });
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
            }

            return View(model);
        }

        private void BuildTimeSpanSelect(PlanningOverviewModel model)
        {
            var timeWindows = _logisticsService.GetAllShippingTimeWindows().OrderBy(d=>d.Id);

            model.AvailableTimeSpans = new List<SelectListItem>
                {
                    new SelectListItem()
                    {
                        Selected = true,
                        Value = "0",
                        Text = "All"
                    },
                    new SelectListItem()
                    {
                         Selected = false,
                         Value = "-1",
                         Text = "Not Scheduled"
                    }
                };

            foreach (var item in timeWindows)
            {
                var select = new SelectListItem()
                {
                    Selected = false,
                    Value = item.Id.ToString(),
                };

                if ((item.NoEarlyThan == 8 && item.NoLateThan == 12) || (item.NoEarlyThan == 12 && item.NoLateThan == 16) || (item.NoEarlyThan == 16 && item.NoLateThan == 21))
                {
                    select.Text = string.Format("Delivery ({0}--{1})", item.NoEarlyThan, item.NoLateThan);
                }

                if ((item.NoEarlyThan == 8 && item.NoLateThan == 11) || (item.NoEarlyThan == 13 && item.NoLateThan == 16))
                {
                    select.Text = string.Format("Pickup  ({0}--{1})", item.NoEarlyThan, item.NoLateThan);
                }

                model.AvailableTimeSpans.Add(select);
            }
        }

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult PendingPackagesOverviewList(GridCommand command, PlanningOverviewModel model)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int[] selectedZoneIds = null;
            if (!String.IsNullOrEmpty(model.LogisticArea))
            {
                var areas = model.LogisticArea.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => int.Parse(x));
                if (areas.All(x => x != 0))
                {
                    selectedZoneIds = areas.ToArray();
                }
            }

            LogisticsWarehouse warehouse = null;
            if (model.WarehouseId > 0)
            {
                warehouse = _logisticsService.GetWarehouseById(model.WarehouseId);
            }

            var districtId = model.SelectedDistrictId;

            // If user is an All-District manager, and he/she selects a warehouse,
            // then that warehouse determines its current districtId for displaying packages.
            // NOTE: If user is a district-specific manager, then the districtId would have been
            //       the same as model.DistrictId anyways.
            if (districtId == 0 && warehouse != null)
            {
                districtId = warehouse.DistrictId;
            }

            District district = null;
            if (districtId != 0)
            {
                district = _logisticsService.GetDistrictById(districtId);
                if (district == null)
                    return AccessDeniedView();
            }

            // TODO: should probably not have retrieved the notReady package in the first place.
            //      (i.e. exclude them via DB query above).
            var shippingStatus = new List<ShippingStatus>() { ShippingStatus.InWarehouse, ShippingStatus.InWarehouseFailedDelivery };
            if (!model.InWarehouseOnly)
                shippingStatus.Add(ShippingStatus.ReadyForPickUp);
            if (model.DisplayNotReady)
                shippingStatus.Add(ShippingStatus.NotYetReady);

            var timeSpans = new List<int>();
            if (model.SelectedTimeSpans != null && model.SelectedTimeSpans.Length > 0)
            {
                if (model.SelectedTimeSpans.Contains(-1))
                {
                    timeSpans.Add(0);
                }
                var timeWindowids = _logisticsService.GetAllShippingTimeWindows().Where(d => model.SelectedTimeSpans.Contains(d.Id)).Select(d => d.Id);
                timeSpans.AddRange(timeWindowids);
            }

            var orderKind = GetRole();

            var packages = _logisticsService.GetPendingPlanPackagesByZone(selectedZoneIds, orderKind, shippingStatus.ToArray(), district, timeSpans.ToArray(), model.IncludeMaxDeliveryAttempt, warehouse, command.Page - 1, command.PageSize);

            var packagesModel = PrepareLogisticsPackageModels(packages, true);

            var gridModel = new GridModel()
            {
                Data = packagesModel,
                Total = packages.TotalCount
            };

            return new JsonResult
            {
                Data = gridModel
            };
        }

        [HttpPost]
        public JsonResult CourierList(int districtId)
        {
            District district = null;
            if (districtId > 0)
            {
                district = _logisticsService.GetDistrictById(districtId);
                if (district == null)
                {
                    return new JsonResult
                    {
                        Data = new List<SelectListItem>()
                    };
                }
            }

            var couriers = _logisticsService.GetCouriers(district);

            var courierSelectList = PrepareCourierSelectList(couriers, false);

            return new JsonResult
            {
                Data = courierSelectList,
            };
        }

        [HttpPost]
        public JsonResult WarehouseList(int districtId)
        {
            var district = _logisticsService.GetDistrictById(districtId);
            var warehouseList = _logisticsService.GetAllWarehouses(district);

            var warehouseSelectList = new List<SelectListItem>(warehouseList.Count());
            warehouseList.ForEach(w =>
            {
                var currItem = new SelectListItem()
                {
                    Value = w.Id.ToString(),
                    Text = String.Format("Warehouse: {0}", w.Address.FormattedAddress()),
                };

                warehouseSelectList.Add(currItem);
            });

            if (warehouseList.Count == 1)
            {
                warehouseSelectList.First().Selected = true;
            }

            return new JsonResult
            {
                Data = warehouseSelectList,
            };
        }

        public ActionResult Plan(string selectedIds, string courierId, string warehouseId)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var customer = _workContext.CurrentCustomer;
            int courierIdNum;
            if (String.IsNullOrEmpty(courierId) || !Int32.TryParse(courierId, out courierIdNum))
            {
                ErrorNotification(String.Format("courierId string parameter expected to contain integer numeric value but contains ({0})", courierId));
                return RedirectToAction("PlanningOverview");
            }

            int warehouseIdNum;
            if (String.IsNullOrEmpty(warehouseId) || !Int32.TryParse(warehouseId, out warehouseIdNum))
            {
                ErrorNotification(String.Format("warehouseId string parameter expected to contain integer numeric value but contains ({0})", warehouseId));
                return RedirectToAction("PlanningOverview");
            }

            var ids = selectedIds
                .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x => Convert.ToInt32(x))
                .ToArray();

            if (String.IsNullOrEmpty(selectedIds) ||
                ids.Count() == 0)
            {
                ErrorNotification("One or more Logistics Packages must be selected for planning");
                return RedirectToAction("PlanningOverview");
            }

            var warehouse = _logisticsService.GetWarehouseById(warehouseIdNum);
            if (warehouse == null)
            {
                ErrorNotification(String.Format("Cannot find warehouse with warehouseId ({0})", warehouseIdNum));
                return RedirectToAction("PlanningOverview");
            }

            var courier = _logisticsService.GetCourierById(courierIdNum);

            // A courier can only be assigned to one active (i.e. not yet completed) plan at a time.
            // Any packages added to a courier who is associated with an active plan will be assigned
            // to that active plan.
            // If a courier is not associated with an active plan, then a new plan is created for the 
            // packages.
            var plan = _logisticsService.GetPlanByDriverId(courierIdNum);

            // Plan has been checked out
            if ((plan != null) && (plan.Status == PlanStatus.Locked))
            {
                ErrorNotification(String.Format("The plan is currently being changing."));
                return RedirectToAction("PlanningOverview");
            }

            // To support multi-district delivery, disable this restriction. Will allow packages to be loaded from other warehouses (either same district or intermediate district).
            /*
            // if plan is an existing one, then it must already been assigned to a warehouse.
            // Must be the same warehouse.
            if ((plan != null) && (plan.WarehouseId != warehouse.Id))
            {
                ErrorNotification(String.Format("Cannot assign packages from warehouseId ({0}) to Plan ({1}) having warehouseId ({2})", warehouse.Id, plan.Id, plan.WarehouseId));
                return RedirectToAction("PlanningOverview");
            }
            */

            // safety check only!
            // check that new packages added (those which are in Warehouse) are all from the same
            // warehouse within themselves.
            IList<LogisticsPackage> pkgsToCheck = new List<LogisticsPackage>(ids.Count());
            foreach (var idToCheck in ids)
            {
                var curPkg = _logisticsService.GetPackageById(idToCheck);
                pkgsToCheck.Add(curPkg);
            }

            int commonWarehouseId = -1;
            var pkgsInSameWarehouse = _logisticsService.PackagesInSameWarehouse(pkgsToCheck, out commonWarehouseId);
            if (!pkgsInSameWarehouse)
            {
                ErrorNotification("One or more In-Warehouse Logistics Packages selected for planning are from multiple warehouses.");
                return RedirectToAction("PlanningOverview");
            }
            else if ((commonWarehouseId > 0) && (commonWarehouseId != warehouse.Id))
            {
                ErrorNotification("Packages are from different warehouse then the selected warehouse.");
                return RedirectToAction("PlanningOverview");
            }

            if (plan == null)
            {
                // create a logistics plan
                plan = _logisticsService.CreateLogisticsPlan(courier, warehouse, customer.Id);
            }

            int planId = plan.Id;

            // attach this plan into each logisticsLocation record, indicating those locations
            // (and by extension their associated packages) are now being planned under "this" plan
            _logisticsService.AssignLogisticsPlanToPackages(ids, planId, customer.Id);

            // change the plan status to new/pending until manager checks in the new packages into
            // the plan
            plan.Status = PlanStatus.Locked;
            _logisticsService.UpdateLogisticsPlan(plan, customer.Id);

            //update order shippingStatus
            UpdateOrderShippingStatus(pkgsToCheck);

            return RedirectToAction("RoutePlanning", new { id = planId, ignoreStatus = true });
        }

        private void UpdateOrderShippingStatus(IList<LogisticsPackage> pkgs)
        {
            if (pkgs == null || pkgs.Count <= 0) return;

            foreach (var item in pkgs)
            {
                var order = item.Shipment.Order;

                UpdateOrderAndPkgStatus(order, item);

                if (order != null && order.BoundOrderId != null)
                {
                    var cOrder = _orderService.GetOrderById(order.BoundOrderId.Value);
                    if (cOrder == null) continue;

                    UpdateOrderAndPkgStatus(cOrder, item);
                }
            }
        }

        private void UpdateOrderAndPkgStatus(Order order, LogisticsPackage pkg)
        {
            if (order == null || pkg == null || order.ShippingStatus != ShippingStatus.NotYetReady) return;

            order.ShippingStatus = ShippingStatus.ReadyForPickUp;

            order.OrderNotes.Add(new OrderNote()
            {
                CreatedOnUtc = DateTime.UtcNow,
                DisplayToCustomer = true,
                Note = string.Format("order:{0} ShippintStatus update to {1}", order.Id, order.ShippingStatus.ToString()),
                OrderId = order.Id
            });

            _orderService.UpdateOrder(order);

            if (pkg.Status == ShippingStatus.NotYetReady)
            {
                var package = _logisticsService.GetPackageById(pkg.Id);
                package.Status = ShippingStatus.ReadyForPickUp;

                _logisticsService.UpdatePackage(package);
            }
        }

        public ActionResult PlanList()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var defaultPlanStatus = new[] { PlanStatus.Locked, PlanStatus.Running };

            //var plans = _logisticsService.GetPlans(defaultPlanStatus, null, null, null);
            //// TODO: Refactor
            //var logisticsPlanModelList = plans.Select(x =>
            //{
            //    var planModel = x.ToModel();
            //    planModel.StatusString = ((PlanStatus)planModel.Status).GetLocalizedEnum(_localizationService, _workContext);
            //    return planModel;
            //}).ToList();

            var model = new LogisticsPlanListModel();
            model.SelectedPlanStatus = defaultPlanStatus.Select(x => (int)x).ToArray();

            model.AvailablePlanStatus.ForEach(pl => model.AvailablePlanStatusStrings.Add(pl.GetLocalizedEnum(_localizationService, _workContext)));

            //model.Plans = new GridModel<LogisticsPlanModel>()
            //{
            //    Data = new PagedList<LogisticsPlanModel>(logisticsPlanModelList, 0, 15),
            //    Total = plans.Count
            //};

            return View(model);
        }

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult PlanList(GridCommand command, LogisticsPlanListModel model,
            [ModelBinderAttribute(typeof(CommaSeparatedModelBinder))] int[] selectedPlanStatus)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            DateTime? startDateValue = (model.StartDate == null) ? null
                            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone);

            DateTime? endDateValue = (model.EndDate == null) ? null
                            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone);

            var planStatusArray = new[] { PlanStatus.Locked, PlanStatus.Running };
            if (selectedPlanStatus.Length > 0)
            {
                planStatusArray = selectedPlanStatus.Select(x => (PlanStatus)x).ToArray();
            }

            var plans = _logisticsService.GetPlans(planStatusArray, startDateValue, endDateValue, null);

            // show only plan accessible to the user.
            if (district != null)
            {
                plans = plans.Where(pl => _logisticsService.PlanAccessibleToDistrict(pl, district)).ToList();
            }

            IEnumerable<LogisticsPlanModel> planModels = plans.Select(x =>
            {
                var planModel = x.ToModel();
                planModel.StatusString = ((PlanStatus)planModel.Status).GetLocalizedEnum(_localizationService, _workContext);
                return planModel;
            });

            var gridModel = new GridModel<LogisticsPlanModel>()
            {
                Data = new PagedList<LogisticsPlanModel>(planModels.ToList(), command.Page - 1, command.PageSize),
                Total = plans.Count
            };

            return new JsonResult
            {
                Data = gridModel
            };
        }

        public ActionResult PlanEdit(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var plan = _logisticsService.GetPlanByPlanId(id);
            if (plan == null)
            {
                ErrorNotification(String.Format("Cannot retrieve Logistics Plan with Id ({0})", id));
                return RedirectToAction("PlanList");
            }

            var model = plan.ToModel();

            // every associated planNote was properly auto-converted excpet for Creation Time 
            // which needs to be converted to user's time from Utc.
            var pnIdx = 0;
            foreach (var pn in plan.PlanNotes)
            {
                // convert to user's time for display later.
                model.PlanNotes[pnIdx].CreatedOn = _dateTimeHelper.ConvertToUserTime(pn.CreatedOnUtc, DateTimeKind.Utc).ToString("G");
                pnIdx++;
            }

            model.StatusString = ((PlanStatus)model.Status).GetLocalizedEnum(_localizationService, _workContext);
            return View(model);
        }

        [HttpPost, ParameterBasedOnFormNameAttribute("force-checkout", "forceCheckout")]
        public ActionResult PlanEdit(LogisticsPlanModel model, bool forceCheckout)
        {
            var customer = _workContext.CurrentCustomer;
            if (!ModelState.IsValid)
            {
                return RedirectToAction("PlanList");
            }

            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var plan = _logisticsService.GetPlanByPlanId(model.Id);
            if (forceCheckout && plan != null)
            {
                plan.Status = PlanStatus.Running;
                _logisticsService.UpdateLogisticsPlan(plan, customer.Id);

                // since the purpose of this forceCheckout is to checkin so that it can 
                // be immediately checked out, might as well check-out right away then.
                SuccessNotification("The plan has been force-checked out to you, and can now be edited normally.");
                return RedirectToAction("RoutePlanning", new { id = plan.Id });
            }

            var prevStatus = plan.Status;
            var modelStatus = (PlanStatus)model.Status;

            // update status of current plan, only if plan status is really changing to a different state
            if (prevStatus != modelStatus)
            {
                // If request to set plan status to completed, then all packages in that plan
                // must be not in a transient state. That is, all the packages ready for re-planning.
                if (modelStatus == PlanStatus.Closed)
                {
                    var pkgList = _logisticsService.GetPackagesByPlanId(model.Id);
                    var packagesInTransientState = _logisticsService.PackagesInTransientState(pkgList);

                    if (packagesInTransientState > 0)
                    {
                        ErrorNotification(String.Format("Cannot set Logistics Plan with Id {0} to status {1} when package(s) still in transient state.", model.Id, PlanStatus.Closed.GetLocalizedEnum(_localizationService, _workContext)));
                        return RedirectToAction("PlanEdit", new { id = plan.Id });
                    }
                }

                // if manager is trying to change a plan from Completed status to other status
                // Then need to make sure no other plans for the same courier is currently in non-Completed status state.
                if (prevStatus == PlanStatus.Closed && plan.CourierId.HasValue)
                {
                    var nonCompletedPlan = _logisticsService.GetPlanByDriverId(plan.CourierId.Value);

                    if (nonCompletedPlan != null)
                    {
                        ErrorNotification(String.Format("Cannot set Logistics Plan with Id {0} to status {1} when other Plan(s) associated the same Courier are not in Completed state.", model.Id, modelStatus.GetLocalizedEnum(_localizationService, _workContext)));
                        return RedirectToAction("PlanEdit", new { id = plan.Id });
                    }
                }

                // if changing to In Progress, then plan content (i.e. the packages and their locations)
                // must be properly setup.
                if (modelStatus == PlanStatus.Running)
                {
                    var errorList = _logisticsService.ValidateLogisticsPlan(plan);
                    if (errorList != null && errorList.Count > 0)
                    {
                        errorList.ToList().ForEach(x => ErrorNotification(x));
                        return RedirectToAction("PlanEdit", new { id = plan.Id });
                    }
                }

                plan.Status = modelStatus;
                _logisticsService.UpdateLogisticsPlan(plan, customer.Id);
                SuccessNotification("The plan status has been changed successfully.");
            }

            // go back to this page to see result.
            return RedirectToAction("PlanEdit", plan.Id);
        }

        public ActionResult RoutePlanning(int id, bool? ignoreStatus)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var plan = _logisticsService.GetPlanByPlanId(id);
            if (plan == null)
            {
                ErrorNotification(String.Format("Cannot retrieve Logistics Plan with planId ({0})", id));
                return RedirectToAction("PlanningOverview");
            }

            if (plan.Status == PlanStatus.Closed)
            {
                ErrorNotification(String.Format("Cannot re-plan a Completed Plan of Id ({0})", id));
                return RedirectToAction("PlanList");
            }

            if (!ignoreStatus.GetValueOrDefault(false) && plan.Status == PlanStatus.Locked)
            {
                ErrorNotification(String.Format("The plan is currently being changing."));
                return RedirectToAction("PlanList");
            }
            else
            {
                if (plan.Status == PlanStatus.Running)
                {
                    plan.Status = PlanStatus.Locked;
                    _logisticsService.UpdateLogisticsPlan(plan, _workContext.CurrentCustomer.Id);
                }
            }

            LogisticsRoutePlanningModel model;
            try
            {
                model = PrepareLogisticsRoutePlanningModel(plan);

                model.MapContext = new MapContextModel();
                model.MapContext.ShowRoute = false;
                model.MapContext.SequentialLocations = GetLocationsByPlan(plan, false, false);

                if (!String.IsNullOrEmpty(_logisticsInformationSettings.BingMapKey))
                {
                    model.MapContext.MapApiKey = _logisticsInformationSettings.BingMapKey;
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PlanningOverview");
            }

            return View(model);
        }

        [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")]
        public ActionResult RoutePlanning(LogisticsRoutePlanningModel model, bool continueEditing)
        {
            if (!ModelState.IsValid)
            {
                return PlanList();
            }

            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var customer = _workContext.CurrentCustomer;

            //LogisticsRoutePlanningModel model2 = null;
            bool showRoute = false;
            bool hasErrors = false;

            LogisticsPlan plan = null;

            try
            {
                plan = _logisticsService.GetPlanByPlanId(model.PlanId);
                if (plan == null)
                {
                    // Technically should not have happened. A RoutePlanning stage would only have worked when there's a plan.
                    hasErrors = true;
                    ErrorNotification(String.Format("Cannot retrieve Logistics Plan with planId ({0})", model.PlanId), false);
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                hasErrors = true;
            }

            if (!hasErrors)
            {
                try
                {
                    if (plan.Status == PlanStatus.Closed)
                    {
                        ErrorNotification(String.Format("Cannot re-plan a Completed Plan of Id ({0})", model.PlanId));
                        return RedirectToAction("PlanList");
                    }

                    string warehouseZone = "Unknown";
                    if (plan != null)
                    {
                        warehouseZone = _logisticsService.GetZoneByZipPostalCode(plan.Warehouse.Address.ZipPostalCode,
                                                                                 plan.Warehouse.Address.Country.TwoLetterIsoCode).Name;
                    }

                    model.Packages.Where(p => p.Removed.HasValue && p.Removed.Value)
                        .ToList()
                        .ForEach(p => RemovePackageFromPlan(model.PlanId, p.PackageId));

                    model.Packages = model.Packages.Where(p => !p.Removed.HasValue || !p.Removed.Value)
                        .ToList();

                    #region Model Validating

                    if (!continueEditing) // if just continue editing, we dont need to verify and generate bothered errors
                    {
                        var usedOrder = new List<int>();
                        var locIds = new List<int>();
                        foreach (var m in model.Packages)
                        {
                            var package = _logisticsService.GetPackageById(m.PackageId);

                            int originLocId;
                            string originLocAddress;
                            int? originLocOrder = null;

                            originLocationRouteInfo(package, model.WarehouseAddrPrefix, plan, warehouseZone,
                                                    out originLocId, out originLocAddress, out originLocOrder);

                            // var originIsWarehouse = originLocAddress.Contains(model.WarehouseAddrPrefix) ? true : false;
                            var originIsWarehouse = (originLocId < 0);

                            m.OriginLocationId = originLocId;
                            m.OriginAddress = originLocAddress;
                            if (originIsWarehouse)
                            {
                                // if warehouse, then we don't want what the user has submitted as rank.
                                m.OriginLocationOrder = null;
                            }
                            else if (_logisticsService.PackagePickupAttempted(package))
                            {
                                // In this case, user was not allowed to rank via the frontend.
                                // Thus, ignoring completely what is in the submitted model.
                                // In this case, overwrite what's in the model by current origin loc order
                                m.OriginLocationOrder = originLocOrder;
                            }

                            // Assumes package's starting point in a plan is either Customer Pickup or Warehouse,
                            // which are both in the same district as plan's Warehouse.
                            // So, no need to add starting point's originLocId to locIds for checking later.

                            /*
                        var destinationIsWarehouse = (m.DestinationLocationId.HasValue && m.DestinationLocationId.Value == LogisticsRoutePlanningModel.WarehouseFakeLocationId)
                                                   ? true : false;  // as returned from view.
                            */

                            var destinationIsWarehouse = (m.DestinationLocationId.HasValue && m.DestinationLocationId.Value < 0) ? true : false;

                            var destinationWarehouseSameAsPlan = false;
                            if (destinationIsWarehouse && -m.DestinationLocationId.Value == plan.Warehouse.Id)
                            {
                                destinationWarehouseSameAsPlan = true;
                            }

                            if (destinationIsWarehouse)
                            {
                                if (destinationWarehouseSameAsPlan)
                                {
                                    // don't need rank if package is actually "being returned" to the same warehouse
                                    // it's coming from (i.e. the driver's origin warehouse).
                                    m.DestinationLocationOrder = null;
                                }
                                else // package planned for another warehouse
                                {
                                    if (_logisticsService.PackageInWarehouse(package) &&
                                        (-m.DestinationLocationId.Value == package.WarehouseId.Value))
                                    {
                                        // package actually already in the other warehouse, don't let rank changed by user.
                                        m.DestinationLocationOrder = package.DeliveryLocation.Rank;
                                    }
                                }
                            }
                            else if (_logisticsService.PackageDeliveryAttempted(package))
                            {
                                // In this case, user was not allowed to rank via the frontend.
                                // Thus, ignoring completely what is in the submitted model.
                                // In this case, overwrite what's in the model by current destination loc rank
                                m.DestinationLocationOrder = package.DeliveryLocation.Rank;
                            }

                            if (m.DestinationLocationId.HasValue)
                            {
                                locIds.Add(m.DestinationLocationId.Value);
                            }

                            if (!originIsWarehouse && !m.OriginLocationOrder.HasValue)
                            {
                                hasErrors = true;
                                ErrorNotification(String.Format("The origin order for package {0} is not set.", m.PackageId), false);
                            }

                            if ((!destinationIsWarehouse || !destinationWarehouseSameAsPlan) && !m.DestinationLocationOrder.HasValue)
                            {
                                hasErrors = true;
                                ErrorNotification(String.Format("The destination order for package {0} is not set.", m.PackageId), false);
                            }

                            if (!originIsWarehouse && (!destinationIsWarehouse || !destinationWarehouseSameAsPlan) && m.DestinationLocationOrder.HasValue &&
                                m.OriginLocationOrder.HasValue)
                            {
                                if (m.DestinationLocationOrder.Value < m.OriginLocationOrder.Value)
                                {
                                    hasErrors = true;
                                    ErrorNotification(String.Format("The origin order for package {0} must be less than dest order.", m.PackageId), false);
                                }
                            }

                            if (!originIsWarehouse && m.OriginLocationOrder.HasValue)
                            {
                                if (usedOrder.Contains(m.OriginLocationOrder.Value))
                                {
                                    hasErrors = true;
                                    ErrorNotification(String.Format("The origin order for package {0} is duplicated.", m.PackageId), false);
                                }
                                else
                                {
                                    usedOrder.Add(m.OriginLocationOrder.Value);
                                }
                            }

                            if ((!destinationIsWarehouse || !destinationWarehouseSameAsPlan) && m.DestinationLocationOrder.HasValue)
                            {
                                if (usedOrder.Contains(m.DestinationLocationOrder.Value))
                                {
                                    hasErrors = true;
                                    ErrorNotification(String.Format("The destination order for package {0} is duplicated.", m.PackageId), false);
                                }
                                else
                                {
                                    usedOrder.Add(m.DestinationLocationOrder.Value);
                                }
                            }
                        }

                        // Check that all the locIds collected above are not going to a mix of within- and across-district.
                        var locsValidForPlan = _logisticsService.LocationsValidForPlan(locIds);
                        if (!locsValidForPlan)
                        {
                            hasErrors = true;
                            ErrorNotification("There must not be a mix of within- and across-district points in a logistics plan.", false);
                        }
                    }
                    #endregion

                    if (ModelState.IsValid && !hasErrors)
                    {
                        // Update the location database table!
                        foreach (var pkgModel in model.Packages)
                        {
                            var pkg = _logisticsService.GetPackageById(pkgModel.PackageId);

                            // var originIsWarehouse = !pkgModel.OriginLocationOrder.HasValue ? true : false;
                            int originLocId;
                            string originLocAddress;
                            int? originLocOrder = null;
                            originLocationRouteInfo(pkg, model.WarehouseAddrPrefix, plan, warehouseZone,
                                                    out originLocId, out originLocAddress, out originLocOrder);

                            var originIsWarehouse = (originLocId < 0);

                            var destinationIsWarehouse = (pkgModel.DestinationLocationId.HasValue && pkgModel.DestinationLocationId.Value < 0)
                                                       ? true : false;  // as returned from view.

                            var destinationWarehouseSameAsPlan = false;
                            if (destinationIsWarehouse && -pkgModel.DestinationLocationId.Value == plan.Warehouse.Id)
                            {
                                destinationWarehouseSameAsPlan = true;
                            }

                            if (destinationIsWarehouse)
                            {
                                // driver has the package, and destination is warehouse,
                                // TODO: What about if package is transient AND cancelled (i.e. pkg.Status = ShipingStatus.Cancelled)
                                //       Need new ability to tell whether cancelled pkg is still outside. If so, need to send to return warehouse.
                                //       Unless, when Cancel is invoked, the check is done there to send outside package to go back to warehouse!
                                if (_logisticsService.PackageInTransientState(pkg))
                                {
                                    if (!_logisticsService.PackageOnWayToWarehouse(pkg))
                                    {
                                        pkg.Status = ShippingStatus.ReturningWarehouse;
                                    }
                                }

                                if (destinationWarehouseSameAsPlan)
                                {
                                    pkg.ToWarehouseId = null;
                                }
                                else
                                {
                                    pkg.ToWarehouseId = -pkgModel.DestinationLocationId.Value;
                                }
                            }
                            else if (pkg.Status == ShippingStatus.ReturningWarehouse)
                            {
                                pkg.Status = ShippingStatus.Delivering;
                            }

                            if (originIsWarehouse)
                            {
                                /* Do nothing
                                pkg.PickUpLocation.PlanId = null;
                                pkg.PickUpLocation.Rank = null;
                                */

                                /* For multi-district delivery with intermediate loading and dispatcher updates,
                                 * pkg.WarehouseId should not be reset to plan's original warehouseId, since
                                 * intermediate package's warehouseId != plan's warehouseId
                                 *
                                // check if package.WarehouseId is plan.Warehouse.Id? If not, set it.
                                // Technically, this is redundant as the warehouseId should have been set already.
                                pkg.WarehouseId = plan.Warehouse.Id;
                                 
                                */
                            }
                            else if (_logisticsService.PackageAtPickupLocation(pkg))
                            {
                                pkg.PickUpLocation.PlanId = model.PlanId;
                                pkg.PickUpLocation.Rank = pkgModel.OriginLocationOrder;
                            }

                            if (destinationIsWarehouse)
                            {
                                // differentiate between same-as-plan-warehouse or another warehouse.
                                if (destinationWarehouseSameAsPlan)
                                {
                                    pkg.DeliveryLocation.Rank = null;

                                    // actually, if the pick up location is also in the same plan as delivery
                                    // location, we do not want the delivery location to show up!
                                    // So, reset delivery location planId to null to hide it from driver.
                                    if (pkg.PickUpLocation.PlanId.HasValue &&
                                        (pkg.PickUpLocation.PlanId.Value == model.PlanId))
                                    {
                                        pkg.DeliveryLocation.PlanId = null;
                                    }
                                    else
                                    {
                                        pkg.DeliveryLocation.PlanId = model.PlanId;
                                    }
                                }
                                else
                                {
                                    pkg.DeliveryLocation.PlanId = model.PlanId;
                                    pkg.DeliveryLocation.Rank = pkgModel.DestinationLocationOrder;
                                }
                            }
                            else if (pkg.Status != ShippingStatus.Delivered)
                            {
                                pkg.DeliveryLocation.PlanId = model.PlanId;
                                pkg.DeliveryLocation.Rank = pkgModel.DestinationLocationOrder;
                                pkg.ToWarehouseId = null;
                            }

                            // do NOT create log, as this method/view is about giving user ability to rank and re-rank.
                            // do not want to log such kinds of updates.
                            _logisticsService.UpdateLogisticsPackage(pkg, PlanNoteStatus.UpdatePackage, customer.Id, createLog: false);
                        }

                        showRoute = !continueEditing; // We dont generate route until clicking the generate route button
                    }
                }
                catch (Exception exc)
                {
                    ErrorNotification(exc);
                    hasErrors = true;
                }
            } // if (!hasErrors)

            LogisticsRoutePlanningModel model2 = null;
            try
            {
                if (plan != null)
                {
                    model2 = PrepareLogisticsRoutePlanningModel(plan);

                    // transferring whatever ranking that has been done so far by user
                    foreach (var pkg in model2.Packages)
                    {
                        foreach (var tpkg in model.Packages)
                        {
                            if (tpkg.PackageId == pkg.PackageId)
                            {
                                pkg.OriginLocationOrder = tpkg.OriginLocationOrder;

                                pkg.DestinationLocationOrder = tpkg.DestinationLocationOrder;
                                pkg.DestinationLocationId = tpkg.DestinationLocationId;
                                pkg.DestinationAddress = tpkg.DestinationAddress;
                            }
                        }

                        // must also make sure it is reflected in the multi-select list.
                        foreach (var avdest in pkg.AvailableDestinationLocations)
                        {
                            var avdestId = int.Parse(avdest.Value);
                            if (avdestId == pkg.DestinationLocationId.Value)
                            {
                                avdest.Selected = true;
                            }
                            else
                            {
                                avdest.Selected = false;
                            }
                        }
                    }

                    model2.MapContext = new MapContextModel();
                    model2.MapContext.ShowRoute = showRoute;

                    // TBD: Make verify = false, when there are errors in validation above.
                    model2.MapContext.SequentialLocations = GetLocationsByPlan(plan, !continueEditing && !hasErrors, showRoute);

                    if (!String.IsNullOrEmpty(_logisticsInformationSettings.BingMapKey))
                    {
                        model2.MapContext.MapApiKey = _logisticsInformationSettings.BingMapKey;
                    }
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
            }

            // Technically, this case should not happen.
            // Otherwise, we would not be able to re-populate the display fields in model either!
            if (model2 == null)
                return View("RoutePlanning", model);

            // clear modelState cached values
            ModelState.Clear();
            return View("RoutePlanning", model2);
        }

        public ActionResult UndoPackageDelivery(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            try
            {
                var pkg = _logisticsService.GetPackageById(id);

                _logisticsProcessingService.RevertDelivered(pkg);

                if (pkg.Status == ShippingStatus.Delivering)
                    SuccessNotification("Undo Package Delivery successful", true);
            }
            catch (Exception ex)
            {
                ErrorNotification(ex);
            }
            return RedirectToAction("PackageEdit", new { id = id });
        }

        public ActionResult UndoPackageShipped(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            try
            {
                var pkg = _logisticsService.GetPackageById(id);

                _logisticsProcessingService.RevertCollected(pkg);

                if (pkg.Status == ShippingStatus.ReadyForPickUp)
                    SuccessNotification("Undo Package Shipped successful", true);
            }
            catch (Exception ex)
            {
                ErrorNotification(ex);
            }
            return RedirectToAction("PackageEdit", new { id = id });
        }

        public ActionResult PlannedRouteList(int id, bool checkInPackagesToPlan = false)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics) &&
                !_permissionService.Authorize(StandardPermissionProvider.ViewLogisticsRoutes))
                return AccessDeniedView();

            var customer = _workContext.CurrentCustomer;

            var plan = _logisticsService.GetPlanByPlanId(id);
            if (plan == null)
            {
                ErrorNotification(String.Format("Cannot retrieve Logistics Plan with planId ({0})", id));
                return RedirectToAction("PlanningOverview");
            }

            // If this action is activated as part of checking in of packages (e.g. via RoutePlanning page),
            // then we would want to update the plan status to Executing (in-progress), if not already.
            // Only allow planStatus changes if plan is not already completed.
            if (_permissionService.Authorize(StandardPermissionProvider.ManageLogistics) &&
                checkInPackagesToPlan &&
                plan.Status != PlanStatus.Running && plan.Status != PlanStatus.Closed)
            {
                plan.Status = PlanStatus.Running;
                _logisticsService.UpdateLogisticsPlan(plan, customer.Id);
            }

            PlannedRouteModel model;
            try
            {
                model = PreparePlannedRouteModel(plan);
                model.MapContext = new MapContextModel();
                model.MapContext.ShowRoute = true;
                model.MapContext.SequentialLocations = GetLocationsByPlan(plan, false, true);

                if (!String.IsNullOrEmpty(_logisticsInformationSettings.BingMapKey))
                {
                    model.MapContext.MapApiKey = _logisticsInformationSettings.BingMapKey;
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PlanList");
            }

            return View(model);
        }

        public ActionResult ShowCourierCommission()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            LogisticsCourierCommissionModel model = new LogisticsCourierCommissionModel();
            try
            {
                model.DistrictId = districtId.Value;
                model.DistrictName = district != null ? district.Name : _localizationService.GetResource("Admin.Logistics.Common.Fields.AllDistricts");
            }
            catch (Exception exc)
            {
                ErrorNotification(exc, false);
                return AccessDeniedView();
            }
            return View(model);
        }

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult ShowCourierCommissionList(GridCommand command, LogisticsCourierCommissionModel model)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content("Access denied");

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return Content("Access denied");

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return Content("Access denied");
            }

            try
            {
                int totalFailPickUp = 0;
                int totalFailDelivery = 0;
                int totalSuccessDelivery = 0;
                int totalSuccessPickUp = 0;

                if (model.CourierId > 0 && model.StartDate.HasValue && model.EndDate.HasValue &&
                    model.EndDate.Value >= model.StartDate.Value)
                {
                    DateTime? startDateValue = (model.StartDate == null) ? null
                                    : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone);

                    DateTime? endDateValue = (model.EndDate == null) ? null
                                    : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1);

                    var plans = _logisticsService.GetPlans(new PlanStatus[] { PlanStatus.Closed },
                                                               startDateValue, endDateValue, model.CourierId);

                    // show only plan accessible to the user.
                    if (district != null)
                    {
                        plans = plans.Where(pl => _logisticsService.PlanAccessibleToDistrict(pl, district)).ToList();
                    }

                    if (plans != null && plans.Count() > 0)
                    {
                        foreach (var plan in plans)
                        {
                            var planModel = PreparePlanModelForCommission(plan);

                            var sumFailPickUp = planModel.PackageLocations.Where(p => p.ShippingStatus == ShippingStatus.FailedPickUp).Count();
                            var sumFailDelivery = planModel.PackageLocations.Where(p => p.ShippingStatus == ShippingStatus.ReturningWarehouseFailedDelivery).Count();
                            var sumSuccessDelivery = planModel.PackageLocations.Where(p => p.ShippingStatus == ShippingStatus.Delivered).Count();
                            var sumSuccessPickUp = planModel.PackageLocations.Where(p => p.ShippingStatus == ShippingStatus.ReturningWarehouse).Count();

                            planModel.CustomProperties.Add("SumFailPickUp", sumFailPickUp);
                            planModel.CustomProperties.Add("SumFailDelivery", sumFailDelivery);
                            planModel.CustomProperties.Add("SumSuccessDelivery", sumSuccessDelivery);
                            planModel.CustomProperties.Add("SumSuccessPickUp", sumSuccessPickUp);

                            totalFailDelivery += sumFailDelivery;
                            totalFailPickUp += sumFailPickUp;
                            totalSuccessDelivery += sumSuccessDelivery;
                            totalSuccessPickUp += sumSuccessPickUp;
                            model.Plans.Add(planModel);
                        }
                    }
                }

                var plansModel = new PagedList<PlannedRouteModel>(model.Plans, command.Page - 1, command.PageSize);
                var gridModel = new GridModel<PlannedRouteModel>
                {
                    Data = plansModel,
                    Total = plansModel.TotalCount
                };

                var aggregator = new PlannedRouteModel()
                {
                    aggregatorfaildelivery = totalFailDelivery,
                    aggregatorfailpickup = totalFailPickUp,
                    aggregatorsuccessdelivery = totalSuccessDelivery,
                    aggregatorsuccesspickup = totalSuccessPickUp
                };
                gridModel.Aggregates = aggregator;

                return new JsonResult
                {
                    Data = gridModel
                };

            }
            catch (Exception exc)
            {
                ErrorNotification(exc, false);
                return Content("Access denied");
            }
        }

        [GridAction(EnableCustomBinding = true)]
        public ActionResult GetPackageLocations(int planId)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content("Access denied");

            try
            {
                // packageList has a list of packages ordered by package Id.
                var packagesList = _logisticsService.GetPackagesFromPlanNotesByPlanId(planId);

                var pkgLocationList = new List<PackageLocationModel>();
                foreach (var pkg in packagesList)
                {
                    // planNotes contains notes only for this package while it was being associated with this plan.
                    // The plan note list is ordered in creation time.
                    var planNotes = _logisticsService.GetPlanNotesByPlanId(planId);
                    if (planNotes != null)
                    {
                        planNotes = planNotes
                            .Select(pn =>
                            {
                                var ns = pn.Note.Split(new char[] { '|' });

                                int? packageId = null;
                                ShippingStatus? packageStatus = null;
                                if (ns.Count() > 3)
                                {
                                    packageId = Int32.Parse(ns[3]);
                                    packageStatus = (ShippingStatus?)Int32.Parse(ns[4]);
                                }

                                return new { PlanNote = pn, PackageId = packageId, PackageStatus = packageStatus };
                            })
                            .Where(x => x.PackageId == pkg.Id)
                            .Select(x =>
                                {
                                    var planNote = x.PlanNote;
                                    if (!planNote.LocationId.HasValue)
                                    {
                                        //assign locationId
                                        if (x.PackageStatus == ShippingStatus.ReturningWarehouse || x.PackageStatus == ShippingStatus.InWarehouse
                                            || x.PackageStatus == ShippingStatus.FailedPickUp)
                                        {
                                            planNote.LocationId = pkg.PickUpLocationId;
                                        }
                                        else if (x.PackageStatus == ShippingStatus.Delivering || x.PackageStatus == ShippingStatus.InWarehouseFailedDelivery
                                            || x.PackageStatus == ShippingStatus.Delivered || x.PackageStatus == ShippingStatus.ReturningWarehouseFailedDelivery)
                                        {
                                            planNote.LocationId = pkg.DeliveryLocationId;
                                        }
                                    }
                                    return planNote;
                                })
                            .ToList();
                        var pkgLocModelList = PreparePackageLocationModelList(planNotes);
                        pkgLocationList.AddRange(pkgLocModelList);
                    }
                }

                var gridModel = new GridModel<PackageLocationModel>()
                {
                    Data = pkgLocationList,
                    Total = pkgLocationList.Count
                };
                return new JsonResult
                {
                    Data = gridModel
                };
            }
            catch (Exception exc)
            {
                ErrorNotification(exc, false);
                return Content("Access denied");
            }
        }

        public ActionResult ShowPlanHistory()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            var model = new LogisticsPlanHistoryModel();

            var couriers = _logisticsService.GetCouriers(district);
            model.AvailableCouriers = PrepareCourierSelectList(couriers);

            return View(model);
        }

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult ShowPlanHistory(GridCommand command, LogisticsPlanHistoryModel model)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var gridModel = new GridModel<PackageLocationModel>();

            // case where courier not yet selected, or even invalid courier id.
            if (model.CourierId <= 0)
            {
                gridModel.Data = new List<PackageLocationModel>();
                gridModel.Total = 0;

                return new JsonResult
                {
                    Data = gridModel
                };
            }

            DateTime? startDateUtc = (model.StartDate == null) ? null
                            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone);

            DateTime? endDateUtc = (model.EndDate == null) ? null
                            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1);

            var planNotes = _logisticsService.SearchPlanNotes(startDateUtc,
                                                              endDateUtc,
                                                              model.CourierId,
                                                              true,
                                                              command.Page - 1,
                                                              command.PageSize);

            var pkgLocModelList = PreparePackageLocationModelList(planNotes);

            gridModel.Data = pkgLocModelList;
            gridModel.Total = planNotes.TotalCount;

            return new JsonResult
            {
                Data = gridModel
            };
        }


        #endregion

        #region Utilities

        [NonAction]
        protected void UpdateLocales(PackageNoteTemplate packageNoteTemplate, LogisticsPackageNoteTemplateModel model)
        {
            foreach (var localized in model.Locales)
            {
                _localizedEntityService.SaveLocalizedValue(packageNoteTemplate,
                                                               x => x.Content,
                                                               localized.Content,
                                                               localized.LanguageId);
            }
        }

        [NonAction]
        private IList<SelectListItem> PrepareCourierSelectList(IList<LogisticsCourier> couriers, bool addSelectLine = true)
        {
            if (couriers == null)
                throw new ArgumentNullException("couriers");

            var courierSelectList = new List<SelectListItem>(couriers.Count());

            foreach (var c in couriers)
            {
                var cust = c.Customer;
                string districtName = "Unknown";
                if (c.DistrictIds != null && c.DistrictIds.Length > 0)
                {
                    districtName = String.Join(",",
                        c.DistrictIds.Select(x =>
                        {
                            var d = _logisticsService.GetDistrictById(x);
                            return d == null ? "Unknown" : d.Name;
                        }));
                }

                var currItem = new SelectListItem()
                {
                    Value = cust.Id.ToString(),
                    Text = String.Format("{0} ({1}) (District: {2})", cust.GetFullName(), cust.Email, districtName),
                };

                courierSelectList.Add(currItem);
            }

            if (addSelectLine)
            {
                courierSelectList.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Logistics.SelectCourier"), Value = "0" });
            }

            return courierSelectList;
        }


        [NonAction]
        private LogisticsRoutePlanningModel PrepareLogisticsRoutePlanningModel(LogisticsPlan plan)
        {
            var model = new LogisticsRoutePlanningModel();

            if (plan != null)
            {
                model.PlanId = plan.Id;

                if (plan.CourierId.HasValue)
                {
                    model.CourierId = plan.CourierId.Value;

                    var cName = plan.Courier.Customer.GetFullName();
                    model.CourierName = !String.IsNullOrWhiteSpace(cName) ? cName : plan.Courier.Customer.Email;
                }
                else
                {
                    model.CourierId = 0;
                    model.CourierName = "";
                }
                // TODO: this is code compiling workaround. should be resolved by list all possible warehouse addresses
                model.WarehouseId = plan.WarehouseId;

                // TODO: Although the DB schema says that a Warehouse is optional for a Plan, in practice, a plan should ALWAYS have a Warehouse associated!
                //       The associated warehouse is NOT supposed to change after a plan is created.
                //       Thus, I find the check below redundant. IF a check is needed, then it should report and errorNotification!
                model.WarehouseAddress = plan.Warehouse == null ? new AddressModel() : plan.Warehouse.Address.ToModel();

                // get warehouseZone now to save having to do this query for each package
                // inside the PrepareLogisticsRoutePackageModel() method.
                var warehouseZone = _logisticsService.GetZoneByZipPostalCode(plan.Warehouse.Address.ZipPostalCode,
                                                                             plan.Warehouse.Address.Country.TwoLetterIsoCode).Name;

                var pkgs = _logisticsService.GetPackagesByPlanId(plan.Id);
                var refTimeUtc = DateTime.UtcNow;
                model.Packages = pkgs.Select(x =>
                {
                    var routePkgModel = new LogisticsRoutePackageModel();
                    PrepareLogisticsRoutePackageModel(model.WarehouseAddrPrefix, warehouseZone, routePkgModel, x, refTimeUtc, plan);
                    return routePkgModel;
                }).ToList();


                // TODO: Investigate the pkgsInTransientState portion. Kind of weird!
                //       ACTUALLY not really being used right now anywhere. So, let's comment it out.
                /*
                var pkgsInTransientState = _logisticsService.PackagesInTransientState(pkgs);

                // prevent route re-generation if any existing packages already in transient state
                // or plan is not in planning stage.
                if ((plan.Status != PlanStatus.NewlyPlanned) || (pkgsInTransientState > 0))
                {
                    model.AllowRouteGeneration = false;
                }
                 */
            }

            return model;
        }

        [NonAction]
        private void PrepareLogisticsRoutePackageModel(string warehouseAddrPrefix, string warehouseZone, LogisticsRoutePackageModel model, LogisticsPackage package, DateTime refTimeUtc, LogisticsPlan plan)
        {
            if (model == null)
                throw new ArgumentNullException("model");

            if (package == null)
                throw new ArgumentNullException("package");

            model.PackageId = package.Id;
            model.Status = package.Status;
            model.Removed = null;
            model.Attribute = package.Shipment.Attributes.HasValue && (package.Shipment.Attributes.Value != 0) ?
                ((AddtionalAttributes)package.Shipment.Attributes.Value).GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id) : "";

            if (_logisticsService.PackageInStaticStateButStillInProcess(package))
            {
                model.Removed = false;
            }

            //TimeSpan? timeElapsedSinceOrdered = null;
            //string timeElapsedSinceOrderedStr = null;
            //ComputePackageTimeElapsedSinceOrdered(refTimeUtc, package,
            //                                      out timeElapsedSinceOrdered,
            //                                      out timeElapsedSinceOrderedStr);

            //model.TimeElapsedSinceOrdered = timeElapsedSinceOrdered;
            //model.TimeElapsedSinceOrderedStr = timeElapsedSinceOrderedStr;

            // should be null if package is currently not inside a warehouse.
            if (_logisticsService.PackageInWarehouse(package))
            {
                model.WarehouseId = package.WarehouseId;
                model.WarehouseAddress = package.Warehouse.Address.FormattedAddress();
            }

            // Define where the package is starting from.
            int originLocId;
            string originLocAddress;
            int? originLocOrder = null;
            originLocationRouteInfo(package, warehouseAddrPrefix, plan, warehouseZone, out originLocId, out originLocAddress, out originLocOrder);

            model.OriginLocationId = originLocId;   // negative if it's a warehouse.
            model.OriginAddress = originLocAddress;
            model.OriginLocationOrder = originLocOrder;

            var pickupLocation = package.PickUpLocation;
            model.OriginType = pickupLocation.Address.Type.HasValue ? pickupLocation.Address.Type.Value.GetLocalizedEnum(_localizationService, _workContext) : "Unknown";

            var deliveryLocation = package.DeliveryLocation;
            model.DestinationType = deliveryLocation.Address.Type.HasValue ? deliveryLocation.Address.Type.Value.GetLocalizedEnum(_localizationService, _workContext) : "Unknown";

            var deliveryZone = _logisticsService.GetZoneByZipPostalCode(deliveryLocation.Address.ZipPostalCode,
                                                                        deliveryLocation.Address.Country.TwoLetterIsoCode).Name;

            // TODO: IF pkg is cancelled while not yet delivered, then:
            //       - Should not allow pkg to be planned if pkg is still not out for delivery.
            //       - If already out but not delivered yet, then should send pkg back to warehouse. (only choice!)
            //       - if already delivered, then continue to show the delivered status. (too late to cancel).

            // always add the final delivery location first so it shows up as first option.
            if (package.Status != ShippingStatus.Cancelled)
            {
                model.AvailableDestinationLocations.Add(new SelectListItem()
                {
                    Text = String.Format("{0}: {1}", deliveryZone, deliveryLocation.Address.FormattedAddress()),
                    Value = deliveryLocation.Id.ToString(),

                    // when package.ToWarehouseId has no value, then package is NOT going to warehouse.
                    Selected = deliveryLocation.PlanId.HasValue && deliveryLocation.PlanId.Value == plan.Id && !package.ToWarehouseId.HasValue && package.Status != ShippingStatus.ReturningWarehouse ? true : false
                });
            }

            // TODO: Revisit the toWarehouseSameAsPlan logic below!!
            // add plan's warehouse only if delivery not yet attempted, and if package is not currently in the plan's warehouse.
            bool toWarehouseSameAsPlan = false;
            if (!_logisticsService.PackageDeliveryAttempted(package) &&
                !(_logisticsService.PackageInWarehouse(package) && package.WarehouseId.Value == plan.Warehouse.Id))
            {
                toWarehouseSameAsPlan =
                    !package.ToWarehouseId.HasValue && // Package is not going to other warehouse
                    (
                    // ReadyForPickup: Pickup location is in the plan but delivery location is not.
                        (package.Status == ShippingStatus.ReadyForPickUp && package.PickUpLocation.PlanId == plan.Id && !deliveryLocation.PlanId.HasValue) ||
                    // FailedPickup: Pickup location is in the plan but delivery location is not.
                        (package.Status == ShippingStatus.FailedPickUp && package.PickUpLocation.PlanId == plan.Id && !deliveryLocation.PlanId.HasValue) ||
                    // ReturningWarehouse: Pickup location is in the plan. (i.e. not starting out from warehouse)
                        (package.Status == ShippingStatus.ReturningWarehouse && package.PickUpLocation.PlanId == plan.Id && !deliveryLocation.PlanId.HasValue) ||
                    // Cancelled (i.e. prior to pickup): Pickup location is in the plan but delivery location is not.
                        (package.Status == ShippingStatus.Cancelled && package.PickUpLocation.PlanId == plan.Id && !deliveryLocation.PlanId.HasValue) ||
                    // Other status: delivery location is in the plan
                        (package.Status != ShippingStatus.ReadyForPickUp && deliveryLocation.PlanId.HasValue && deliveryLocation.PlanId.Value == plan.Id && !deliveryLocation.Rank.HasValue)
                    );

                model.AvailableDestinationLocations.Add(new SelectListItem()
                {
                    Text = String.Format("{0} District {1}: {2}", warehouseAddrPrefix, plan.Warehouse.District.Name, plan.Warehouse.Address.FormattedAddress()),
                    Value = (-plan.WarehouseId.Value).ToString(),
                    // Selected = deliveryLocation.PlanId.HasValue && deliveryLocation.PlanId.Value == plan.Id && !package.ToWarehouseId.HasValue && package.Status == ShippingStatus.ReturningWarehouse ? true : false
                    Selected = toWarehouseSameAsPlan
                });
            }

            // if later becomes non-null, means package is going to a warehouse. Otherwise, it's the final destination.
            LogisticsWarehouse warehouseSelected = null;

            var SameDistrictWarehouses = _logisticsService.GetAllWarehouses(plan.Warehouse.District);

            /*
            // if origin location is a warehouse (which has to be the plan's warehouse), then exclude that warehouse
            // from destination selection.
            if (model.OriginLocationId < 0)
            {
            */
            SameDistrictWarehouses = SameDistrictWarehouses.Where(w => w.Id != plan.WarehouseId.Value).ToList();
            /*
            }
            */

            SameDistrictWarehouses.ForEach(w =>
                {
                    if (package.ToWarehouseId.HasValue && package.ToWarehouseId.Value == w.Id)
                    {
                        warehouseSelected = w;
                    }

                    model.AvailableDestinationLocations.Add(new SelectListItem()
                    {
                        Text = String.Format("{0} District {1}: {2}", warehouseAddrPrefix, w.District.Name, w.Address.FormattedAddress()),

                        // warehouse's IDs will be set as negative when treated as location Id.
                        Value = (-(w.Id)).ToString(),

                        // do not care if deliveryLocation.PlanId has value, since it is possible that
                        // package is originally planned to go straight to (any) warehouse.
                        Selected = package.ToWarehouseId.HasValue && package.ToWarehouseId.Value == w.Id ? true : false
                    });
                });

            // if package final location is outside of current plan's district, then
            // must show that district's warehouses too as selection.
            // TBI: For now, to allow multi-district delivery, show all other warehouses in the same region.
            //      This assumes all the cross-districts are within the same region as the origin district.
            if (package.DeliveryLocation.DistrictId.HasValue && package.DeliveryLocation.DistrictId.Value != plan.Warehouse.DistrictId &&
                package.DeliveryLocation.District != null)
            {
                var crossDistrict = package.DeliveryLocation.District;
                var region = crossDistrict.Region;
                var CrossDistrictWarehouses = _logisticsService.GetAllWarehousesInRegion(region);

                // exclude the plan's own warehouse
                CrossDistrictWarehouses = CrossDistrictWarehouses.Where(w => w.Id != plan.WarehouseId).ToList();

                CrossDistrictWarehouses.ForEach(w =>
                {
                    if (package.ToWarehouseId.HasValue && package.ToWarehouseId.Value == w.Id)
                    {
                        warehouseSelected = w;
                    }

                    model.AvailableDestinationLocations.Add(new SelectListItem()
                    {
                        Text = String.Format("{0} District {1}: {2}", warehouseAddrPrefix, w.District.Name, w.Address.FormattedAddress()),

                        // warehouse's IDs will be set as negative when treated as location Id.
                        Value = (-(w.Id)).ToString(),

                        // do not care if deliveryLocation.PlanId has value, since it is possible that
                        // package is originally planned to go straight to (any) warehouse.
                        Selected = package.ToWarehouseId.HasValue && package.ToWarehouseId.Value == w.Id ? true : false
                    });
                });
            }

            if (toWarehouseSameAsPlan)
            {
                model.DestinationAddress = String.Format("{0} District {1}: {2}", warehouseAddrPrefix, plan.Warehouse.District.Name, plan.Warehouse.Address.FormattedAddress());
                model.DestinationLocationId = -plan.Warehouse.Id;
                model.DestinationLocationOrder = null;
            }
            else if (warehouseSelected != null)
            {
                model.DestinationAddress = String.Format("{0} District {1}: {2}", warehouseAddrPrefix, warehouseSelected.District.Name, warehouseSelected.Address.FormattedAddress());
                model.DestinationLocationId = -warehouseSelected.Id; // negative for warehouse.
                model.DestinationLocationOrder = deliveryLocation.Rank;
            }
            // if (deliveryLocation.Rank.HasValue && deliveryLocation.Rank.Value != LogisticsRoutePlanningModel.ReservedOrder)
            //if (deliveryLocation.PlanId.HasValue)
            else
            {
                model.DestinationAddress = String.Format("{0}: {1}", deliveryZone, deliveryLocation.Address.FormattedAddress());
                model.DestinationLocationId = deliveryLocation.Id;
                model.DestinationLocationOrder = deliveryLocation.Rank ?? null;
            }

            // populate each item in the pkgs2 with only its latest package note
            var pkgNote = _logisticsService.GetPackageNotesByPackageId(model.PackageId, 1, new PackageNoteType[] { PackageNoteType.AdminNote }).FirstOrDefault();
            if (pkgNote != null)
            {
                model.LatestPackageNoteContent = pkgNote.Note;
            }

            model.Reference = package.Shipment.Comment;

            model.OverSize = false;
            if (package.Shipment.GetTotalDim() > _logisticsInformationSettings.OversizeDimensionSize)
            {
                model.OverSize = true;
            }

            model.ReadyTime = package.ReadyOnUtc;
            model.ReadyTimeStr = model.ReadyTime != null ? _dateTimeHelper.ConvertToUserTime(model.ReadyTime.Value, DateTimeKind.Utc).ToString("g", new CultureInfo(_workContext.WorkingLanguage.LanguageCulture)) : String.Empty;
            model.PickupEndTime = package.PickupEndOnUtc;
            model.PickupEndTimeStr = model.PickupEndTime != null ? _dateTimeHelper.ConvertToUserTime(model.PickupEndTime.Value, DateTimeKind.Utc).ToString("g", new CultureInfo(_workContext.WorkingLanguage.LanguageCulture)) : String.Empty;

            var order = package.Shipment.Order;

            if (package.Shipment.Order.ShipmentDate.HasValue && package.Shipment.Order.ShippingTimeWindowsId.HasValue)
            {
                //bug 81 show user selected pickup time
                model.PickUpTimeShow = package.Shipment.Order.ShipmentDate.Value.ToString("MM/dd/yyyy") + " " + _logisticsService.GetShippingTimeWindowById(package.Shipment.Order.ShippingTimeWindowsId.Value).Description;
                //model.PickUpTimeShow = _logisticsService.GetShowPickUpTime(order.ShipmentDate, order.ShippingTimeWindowsId, order);
            }
        }

        [NonAction]
        private void originLocationRouteInfo(LogisticsPackage package, string warehouseAddrPrefix, LogisticsPlan plan,
                                             string warehouseZone, out int locId, out string locAddress, out int? locOrder)
        {
            var warehouse = plan.Warehouse;
            var originLoc = _logisticsService.originLocationRouteInfo(package, plan);

            if (originLoc == null)
            {
                // Case: when origin is a warehouse. 
                //       Warehouse does not have a Logistics Location (thus, a null).

                locId = -warehouse.Id; // since warehouse is not a LogisticsLocation, give it a locationId as a negative warehouse.Id
                // Thus, any locId > 0 is "real" locationIds, while locId < 0 is a warehouseId

                locAddress = String.Format("{0} {1}: {2}", warehouseAddrPrefix, warehouseZone, warehouse.Address.FormattedAddress());
                locOrder = null;
            }
            else
            {
                // Otherwise, origin is a logistics location
                var pickupZone = _logisticsService.GetZoneByZipPostalCode(originLoc.Address.ZipPostalCode,
                                                                          originLoc.Address.Country.TwoLetterIsoCode).Name;

                locId = originLoc.Id;
                locAddress = String.Format("{0}: {1}", pickupZone, originLoc.Address.FormattedAddress());
                locOrder = originLoc.Rank;
            }
        }

        // TODO: Update for multi-warehouse and/or multi-district.
        [NonAction]
        private List<MapLocationModel> GetLocationsByPlan(LogisticsPlan plan, bool needVerify = true, bool rankedOnly = true)
        {
            const string warehouse = "Warehouse";
            const string completed = "Completed";
            const string pickup = "PickUp";
            const string delivery = "Delivery";
            const string failedPickup = "FailedPickUp";
            const string failedDelivery = "FailedDelivery";

            var locs = GetSortedLocationsByPlanId(plan.Id, needVerify);

            if (rankedOnly && locs != null)
            {
                // TODO: Should hit here except save and continue scenario when route planning.
                locs = locs.Where(l => l.Rank.HasValue).ToList();
            }

            if (locs == null)
            {
                throw new InvalidOperationException(String.Format("The logistics plan ({0}) cannot be found.", plan.Id));
            }

            var seqLocs = new List<MapLocationModel>();
            var whlm = new MapLocationModel()
            {
                Id = LogisticsRoutePlanningModel.WarehouseFakeLocationId,
                Attribute = warehouse,
                Rank = null,
                PackageId = 0,
                City = plan.Warehouse.Address.City,
                ZipPostalCode = plan.Warehouse.Address.ZipPostalCode,
                AddressId = plan.Warehouse.Address.Id,
                Address = plan.Warehouse.Address.FormattedAddressNoSuite()
            };

            seqLocs.Add(whlm);
            foreach (var loc in locs)
            {
                var lm = new MapLocationModel()
                {
                    Id = loc.Id,
                    Rank = loc.Rank,
                    PackageId = loc.PackageId.Value,
                    City = loc.Address.City,
                    ZipPostalCode = loc.Address.ZipPostalCode,
                    AddressId = loc.AddressId,
                    Address = loc.Address.FormattedAddressNoSuite()
                };

                bool isPickup = loc.Id == loc.Package.PickUpLocationId;

                if (isPickup)
                {
                    lm.Attribute = pickup;
                    //lm.Status = loc.Package.Status == ShippingStatus.ReturningWarehouse || loc.Package.Status == ShippingStatus.Delivering || loc.Package.Status == ShippingStatus.Delivered ? completed : string.Empty;
                    if (_logisticsService.PackagePickedUp(loc.Package))
                    {
                        lm.Status = completed;
                    }
                    else if (loc.Package.Status == ShippingStatus.FailedPickUp)
                    {
                        lm.Status = failedPickup;
                    }
                    else
                    {
                        lm.Status = string.Empty;
                    }
                }
                else
                {
                    lm.Attribute = delivery;
                    if (loc.Package.Status == ShippingStatus.Delivered)
                    {
                        lm.Status = completed;
                    }
                    else if (loc.Package.Status == ShippingStatus.ReturningWarehouseFailedDelivery)
                    {
                        lm.Status = failedDelivery;
                    }
                    else
                    {
                        lm.Status = string.Empty;
                    }
                }

                seqLocs.Add(lm);
            }
            seqLocs.Add(whlm);

            return seqLocs;
        }

        [NonAction]
        private List<LogisticsLocation> GetSortedLocationsByPlanId(int planId, bool needVerify)
        {
            if (_logisticsService.GetPlanByPlanId(planId) == null)
            {
                return null;
            }

            // TODO: need to verify all situations for 
            // ReadyPickup to Warehouse and Delivery location
            // InWarehouse to Delivery location
            // Returning to Warehouse
            var locations = _logisticsService.GetLogisticsLocationsByPlanId(planId);

            if (needVerify)
            {
                if (locations == null)
                {
                    throw new InvalidOperationException("No locations are assigned to the plan.");
                }

                if (locations.Any())
                {
                    //var temp = locations.Where(l => (l.Id == l.Package.PickUpLocationId && !l.Rank.HasValue) ||
                    //                                (l.Id == l.Package.DeliveryLocationId && l.Package.Status != ShippingStatus.ReturningWarehouse && l.Package.ToWarehouse == null && !l.Rank.HasValue));
                    //if (temp != null && temp.Count() != 0)
                    //{
                    //    var errLocs = new System.Text.StringBuilder();
                    //    errLocs.Append("Following location IDs (package IDs) are not ranked: ");
                    //    temp.ToList().ForEach(l => errLocs.Append(String.Format("{0} ({1}), ", l.Id, l.PackageId)));
                    //    throw new InvalidOperationException(errLocs.ToString());
                    //}

                    var temp2 = locations.Where(l => !l.PackageId.HasValue);
                    if (temp2 != null && temp2.Count() != 0)
                    {
                        var errLocs = new System.Text.StringBuilder();
                        errLocs.Append("Following location IDs don't have associated package IDs:");
                        temp2.ToList().ForEach(l => errLocs.Append(String.Format("{0}, ", l.Id)));
                        throw new InvalidOperationException(errLocs.ToString());
                    }
                }
            }

            return locations.ToList();
        }

        [NonAction]
        private PlannedRouteModel PreparePlannedRouteModel(LogisticsPlan plan)
        {
            var model = new PlannedRouteModel();

            if (plan != null)
            {
                model.PlanId = plan.Id;

                var cName = plan.Courier.Customer.GetFullName();
                model.CourierName = !String.IsNullOrWhiteSpace(cName) ? cName : plan.Courier.Customer.Email;

                var locs = _logisticsService.GetLogisticsLocationsByPlanId(plan.Id);

                var ranks = locs.Where(l => l.Rank.HasValue).Select(l => l.Rank.Value);
                var bigRank = 10000;
                if (ranks.Count() > 0)
                {
                    bigRank = bigRank + ranks.Max();
                }

                model.PackageLocations = locs.Select(x =>
                {
                    try
                    {
                        var destinationIsOtherWarehouse = false;
                        if (x.Id == x.Package.DeliveryLocationId &&
                            (x.Package.ToWarehouseId.HasValue || (_logisticsService.PackageInWarehouse(x.Package) && x.Package.WarehouseId.Value != plan.Warehouse.Id)))
                        {
                            destinationIsOtherWarehouse = true;
                        }

                        LogisticsWarehouse warehouse = null;
                        if (destinationIsOtherWarehouse)
                        {
                            warehouse = x.Package.ToWarehouseId.HasValue ? x.Package.ToWarehouse : x.Package.Warehouse;
                        }

                        //var pickupInThisPlan = false;
                        if (x.Package.PickUpLocation.PlanId.HasValue &&
                            x.Package.PickUpLocation.PlanId.Value == plan.Id)
                        {
                            //pickupInThisPlan = true;
                        }


                        var pkgLocModel = new PackageLocationModel()
                        {
                            LocationId = destinationIsOtherWarehouse ? -warehouse.Id : x.Id,
                            AddressId = destinationIsOtherWarehouse ? warehouse.AddressId : x.AddressId,
                            Address = destinationIsOtherWarehouse ? warehouse.Address.ToModel() : x.Address.ToModel(),
                            UpdatedOnUtc = x.UpdatedOnUtc.HasValue ? (DateTime?)x.UpdatedOnUtc.Value : null,
                            UpdatedOn = x.UpdatedOnUtc.HasValue ?
                                        (DateTime?)_dateTimeHelper.ConvertToUserTime(x.UpdatedOnUtc.Value) : null,
                            Rank = x.Rank.HasValue ? (decimal?)x.Rank.Value : bigRank++,

                            LocationStatus = x.Status, // not really used (should not be)
                            Action = x.Id == x.Package.PickUpLocationId ? "Pick Up" : "Delivery",
                            Type = x.Id == x.Package.PickUpLocationId ? "PickUp" : "Delivery",
                            PlanId = x.PlanId.Value,        // must exist
                            CourierId = x.CourierId.Value,  // must exist
                            PackageId = x.PackageId.Value,  // must exist
                            ShippingStatus = x.Package.Status,
                        };
                        if (destinationIsOtherWarehouse)
                        {
                            pkgLocModel.Type = "Warehouse";
                        }

                        pkgLocModel.ReadyTime = x.Package.ReadyOnUtc;
                        pkgLocModel.ReadyTimeStr = pkgLocModel.ReadyTime != null ? _dateTimeHelper.ConvertToUserTime(pkgLocModel.ReadyTime.Value, DateTimeKind.Utc).ToString("g", new CultureInfo(_workContext.WorkingLanguage.LanguageCulture)) : String.Empty;

                        if (destinationIsOtherWarehouse)
                        {
                            pkgLocModel.AddressZone = _logisticsService.GetZoneByZipPostalCode(warehouse.Address.ZipPostalCode,
                                                                                               warehouse.Address.Country.TwoLetterIsoCode).Name;
                        }
                        else
                        {
                            pkgLocModel.AddressZone = _logisticsService.GetZoneByZipPostalCode(x.Address.ZipPostalCode,
                                                                                               x.Address.Country.TwoLetterIsoCode).Name;
                        }

                        // Currently locationStatus is not really being actively updated.
                        // IF it is, then only the first condition below is needed.
                        // For now, we actually need to deduce a location's status based on the package's 
                        // overall status possibilites.
                        // TBI:
                        var pkgStatus = x.Package.Status;
                        if ((pkgLocModel.LocationStatus == LocationStatus.Complete) ||
                            (x.Id == x.Package.DeliveryLocationId &&
                             (_logisticsService.PackageDeliveryAttempted(x.Package) ||
                              (_logisticsService.PackageInWarehouse(x.Package) && x.Package.WarehouseId.Value != plan.Warehouse.Id &&
                               (!x.Package.ToWarehouseId.HasValue || x.Package.ToWarehouseId.Value == x.Package.WarehouseId.Value)) ||
                              pkgStatus == ShippingStatus.FailedPickUp)) ||
                            (x.Id == x.Package.PickUpLocationId && _logisticsService.PackagePickupAttempted(x.Package)))
                        {
                            pkgLocModel.Action += " Completed";
                        }

                        pkgLocModel.Attribute = x.Package.Shipment.Attributes.HasValue && (x.Package.Shipment.Attributes.Value != 0) ?
                            ((AddtionalAttributes)x.Package.Shipment.Attributes.Value).GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id) : "";

                        if (x.Package.Shipment.GetTotalDim() > _logisticsInformationSettings.OversizeDimensionSize)
                        {
                            pkgLocModel.Attribute += "[OVERSIZE]";
                        }

                        pkgLocModel.Reference = x.Package.Shipment.Comment;

                        var pkgNote = _logisticsService.GetPackageNotesByPackageId(pkgLocModel.PackageId, 1, new PackageNoteType[] { PackageNoteType.AdminNote }).FirstOrDefault();
                        if (pkgNote != null)
                        {
                            pkgLocModel.LatestPackageNoteContent = pkgNote.Note;
                        }

                        return pkgLocModel;
                    }
                    catch (Exception ex)
                    {
                        throw new NopException("Location model for location {0} of package {1} cannot be created. Exception: {2}", x.Id, x.PackageId, ex);
                    }
                }).ToList();
            }
            else
                throw new ArgumentNullException("plan");

            return model;
        }

        [NonAction]
        protected CustomerModel PrepareCustomerModelForList(Customer customer)
        {
            return new CustomerModel()
            {
                Id = customer.Id,
                Email = !String.IsNullOrEmpty(customer.Email) ? customer.Email : (customer.IsGuest() ? _localizationService.GetResource("Admin.Customers.Guest") : "Unknown"),
                Username = customer.Username,
                FullName = customer.GetFullName(),
                Company = customer.GetAttribute<string>(SystemCustomerAttributeNames.Company),
                Phone = customer.GetAttribute<string>(SystemCustomerAttributeNames.Phone),
                CellPhone = customer.GetAttribute<string>(SystemCustomerAttributeNames.CellPhone),
                ZipPostalCode = customer.GetAttribute<string>(SystemCustomerAttributeNames.ZipPostalCode),
                CustomerRoleNames = GetCustomerRolesNames(customer.CustomerRoleMappings.Select(crm => crm.CustomerRole).ToList()),
                Active = customer.Active,
                CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc),
                LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc),
            };
        }

        [NonAction]
        protected string GetCustomerRolesNames(IList<CustomerRole> customerRoles, string separator = ",")
        {
            var sb = new System.Text.StringBuilder();
            for (int i = 0; i < customerRoles.Count; i++)
            {
                sb.Append(customerRoles[i].Name);
                if (i != customerRoles.Count - 1)
                {
                    sb.Append(separator);
                    sb.Append(" ");
                }
            }
            return sb.ToString();
        }

        [NonAction]
        private void RemovePackageFromPlan(int planId, int packageId)
        {
            var customer = _workContext.CurrentCustomer;
            var plan = _logisticsService.GetPlanByPlanId(planId);
            var pkg = _logisticsService.GetPackageById(packageId);

            if (_logisticsService.PackageInTransientState(pkg) ||
                pkg.Status == ShippingStatus.Delivered)
            {
                throw new NopException("Cannot remove package {0} from plan {1} because package status = {2}", packageId, planId, pkg.Status.ToString());
            }
            else
            {
                _logisticsService.RemovePackagesFromLogisticsPlan(new int[] { packageId }, planId, customer.Id);
            }
        }

        [NonAction]
        private IEnumerable<LogisticsPackageModel> PrepareLogisticsPackageModels(IEnumerable<LogisticsPackage> packages, bool prepareLatestNote = false)
        {
            var packagesModel = packages.Select(x => PrepareLogisticsPackageModel(x, prepareLatestNote));
            return packagesModel;
        }

        [NonAction]
        protected LogisticsPackageModel PrepareLogisticsPackageModel(LogisticsPackage package, bool prepareLatestNote = false)
        {
            string lUnit = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name;
            string wUnit = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name;

            var pkgModel = package.ToModel();
            if (pkgModel.CustomerAddress == null)
                pkgModel.CustomerAddress = new AddressModel();
            var customer = package.Shipment.Order.Customer;
            pkgModel.CustomerAddress.Email = customer.Email;
            pkgModel.CustomerAddress.Company = customer.GetAttribute<string>(SystemCustomerAttributeNames.Company);
            pkgModel.CustomerAddress.FirstName = customer.GetAttribute<string>(SystemCustomerAttributeNames.FirstName);
            pkgModel.CustomerAddress.LastName = customer.GetAttribute<string>(SystemCustomerAttributeNames.LastName);
            pkgModel.CustomerAddress.CellPhoneNumber = customer.GetAttribute<string>(SystemCustomerAttributeNames.CellPhone);
            pkgModel.CustomerAddress.PhoneNumber = customer.GetAttribute<string>(SystemCustomerAttributeNames.Phone);
            pkgModel.StatusName = ((ShippingStatus)pkgModel.Status).GetLocalizedEnum(_localizationService, _workContext);
            pkgModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(package.CreatedOnUtc, DateTimeKind.Utc);
            pkgModel.CreatedOnStr = pkgModel.CreatedOn.ToString("g");
            pkgModel.UpdatedOn = package.UpdatedOnUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(package.UpdatedOnUtc.Value, DateTimeKind.Utc) : pkgModel.CreatedOn;
            pkgModel.UpdatedOnStr = pkgModel.UpdatedOn.HasValue ? pkgModel.UpdatedOn.Value.ToString("g") : "";
            //pkgModel.ReadyTime = package.ReadyOnUtc.HasValue ? (DateTime?)_dateTimeHelper.ConvertToUserTime(package.ReadyOnUtc.Value, DateTimeKind.Utc) : null;
            //pkgModel.PickupEndTime = package.PickupEndOnUtc.HasValue ? (DateTime?)_dateTimeHelper.ConvertToUserTime(package.PickupEndOnUtc.Value, DateTimeKind.Utc) : null;

            var order = package.Shipment.Order;

            // this should show the user select time only if date and time are selected
            if (package.Shipment.Order.ShipmentDate.HasValue && package.Shipment.Order.ShippingTimeWindowsId.HasValue)
                pkgModel.ScheduledTime = package.Shipment.Order.ShipmentDate.Value.ToString("MM/dd/yyyy") + " " + _logisticsService.GetShippingTimeWindowById(package.Shipment.Order.ShippingTimeWindowsId.Value).Description;
            //pkgModel.ScheduledTime = _logisticsService.GetShowPickUpTime(order.ShipmentDate, order.ShippingTimeWindowsId, order);


            pkgModel.ReadyTimeStr = pkgModel.ReadyTime.HasValue ? pkgModel.ReadyTime.Value.ToString("g") : "";
            pkgModel.PickupEndTimeStr = pkgModel.PickupEndTime.HasValue ? pkgModel.PickupEndTime.Value.ToString("g") : "";

            var pickUpZone = _logisticsService.GetZoneByZipPostalCode(package.PickUpLocation.Address.ZipPostalCode, package.PickUpLocation.Address.Country.TwoLetterIsoCode);
            pkgModel.PickUpZone = pickUpZone == null ? "" : pickUpZone.Name;
            var diliverZone = _logisticsService.GetZoneByZipPostalCode(package.DeliveryLocation.Address.ZipPostalCode,
                package.DeliveryLocation.Address.Country.TwoLetterIsoCode);
            pkgModel.DeliveryZone = diliverZone == null ? string.Empty : diliverZone.Name;

            pkgModel.PickupRank = package.PickUpLocation.Rank;
            pkgModel.DeliveryRank = package.DeliveryLocation.Rank;

            pkgModel.LinearMeasureUnit = lUnit;
            pkgModel.WeightMeasureUnit = wUnit;

            pkgModel.Attribute = package.Shipment.Attributes.HasValue && (package.Shipment.Attributes.Value != 0) ?
                ((AddtionalAttributes)package.Shipment.Attributes.Value).GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id) : "";

            if (package.Shipment.GetTotalDim() > _logisticsInformationSettings.OversizeDimensionSize)
            {
                pkgModel.Attribute += "[OVERSIZE]";
            }

            pkgModel.Reference = package.Shipment.Comment;
            pkgModel.SpotshubPackageSize = package.Shipment.SpotshubPackageSize;

            // populate only its latest package note
            if (prepareLatestNote)
            {
                var pkgNote = _logisticsService.GetPackageNotesByPackageId(package.Id, 1, new PackageNoteType[] { PackageNoteType.AdminNote }).FirstOrDefault();
                pkgModel.LatestPackageNoteContent = pkgNote != null ? pkgNote.Note : null;
            }

            pkgModel.UpdatePackage = true;
            pkgModel.UpdateDeliveryLocation = true;
            pkgModel.UpdatePickUpLocation = true;
            pkgModel.StatusUpdateValidForCommission = false; //default: changes from admin region not valid for commission

            LogisticsPlan plan = _logisticsService.PackageLatestPlan(package);

            if (plan != null)
            {
                pkgModel.PlanId = plan.Id;
                pkgModel.PlanStatusString = plan.Status.GetLocalizedEnum(_localizationService, _workContext);
            }

            pkgModel.SelectedPlanId = 0; // meaning not associated with a plan if not in a plan
            if (plan != null)
                pkgModel.SelectedPlanId = plan.Id;  // otherwise, default to current plan.

            //Google Map
            pkgModel.PickupAddressGoogleMapsUrl = string.Format("http://maps.google.com/maps?f=q&hl=en&ie=UTF8&oe=UTF8&geocode=&q={0}", Server.UrlEncode(package.PickUpLocation.Address.Address1 + " " + package.PickUpLocation.Address.ZipPostalCode + " " + package.PickUpLocation.Address.City + " " + (package.PickUpLocation.Address.Country != null ? package.PickUpLocation.Address.Country.Name : "")));
            pkgModel.DeliveryAddressGoogleMapsUrl = string.Format("http://maps.google.com/maps?f=q&hl=en&ie=UTF8&oe=UTF8&geocode=&q={0}", Server.UrlEncode(package.DeliveryLocation.Address.Address1 + " " + package.DeliveryLocation.Address.ZipPostalCode + " " + package.DeliveryLocation.Address.City + " " + (package.DeliveryLocation.Address.Country != null ? package.DeliveryLocation.Address.Country.Name : "")));

            return pkgModel;
        }

        [NonAction]
        private IEnumerable<LogisticsPackageModel> PrepareLogisticsPackageModels2(IList<LogisticsPackage> packages)
        {
            var packagesModel = PrepareLogisticsPackageModels(packages, true).ToList();

            var availablePlanStatus = new[] { PlanStatus.Locked, PlanStatus.Running };
            var availablePlans = _logisticsService.GetPlans(availablePlanStatus, null, null, null);

            foreach (var model in packagesModel)
            {
                model.AvailablePlanIds.Add(new SelectListItem() { Text = "None", Value = "0", Selected = (model.SelectedPlanId <= 0) ? true : false });
                foreach (var apl in availablePlans)
                {
                    var availPlanDisplayString = String.Format("{0} ( {1} - {2} )", apl.Id.ToString(), apl.Courier.Customer.GetFullName(), apl.Courier.Customer.Email ?? String.Empty);
                    model.AvailablePlanIds.Add(new SelectListItem() { Text = availPlanDisplayString, Value = apl.Id.ToString(), Selected = (model.SelectedPlanId == apl.Id) ? true : false });
                }

                //Clean the package note list and plan note list
                model.PlanNotes = null;
                model.PackageNotes = null;
            }

            return packagesModel;
        }

        [NonAction]
        private void ComputePackageTimeElapsedSinceOrdered(DateTime referenceTimeUtc, LogisticsPackage package,
                                                           out TimeSpan? timeElapsed, out string timeElapsedStr)
        {
            timeElapsed = null;
            timeElapsedStr = null;
            try
            {
                // this service method either returns a non-null value or throws an exception
                timeElapsed = _logisticsService.GetPackageElapsedTimeSinceOrdered(package, referenceTimeUtc);
                timeElapsedStr = timeElapsed.Value.ToString(@"d\ \.\ h\:mm\:ss");
            }
            catch
            {
                // just don't do anything but leave time elapsed as null and display empty later.
            }
        }

        [NonAction]
        private PlannedRouteModel PreparePlanModelForCommission(LogisticsPlan plan)
        {
            if (plan == null)
                throw new ArgumentNullException("plan");

            var model = new PlannedRouteModel()
            {
                PlanId = plan.Id,
            };

            try
            {
                var cName = plan.Courier.Customer.GetFullName();
                model.CourierName = !String.IsNullOrWhiteSpace(cName) ? cName : plan.Courier.Customer.Email;

                // packageList has a list of packages ordered by package Id.
                var packagesList = _logisticsService.GetPackagesFromPlanNotesByPlanId(model.PlanId);

                var tempList = new List<PackageLocationModel>();
                var planNotes = _logisticsService.GetPlanNotesByPlanId(model.PlanId);
                foreach (var pkg in packagesList)
                {
                    // planNotes contains notes only for this package while it was being associated with this plan.
                    // The plan note list is ordered in creation time.
                    //plan notes
                    var planNoteModels = new List<PlanNoteModel>();
                    foreach (var planNote in planNotes)
                    {
                        var pnModel = planNote.ToModel();

                        planNoteModels.Add(pnModel);
                    };
                    planNoteModels = planNoteModels.Where(p => p.PackageId == pkg.Id).ToList();
                    var pkgLocModelList = new List<PackageLocationModel>(planNoteModels.Count());
                    //only need distinct status
                    var distinctPlanNoteModels = planNoteModels.Select(p => p.PackageStatus).Distinct();

                    foreach (var dpn in distinctPlanNoteModels)
                    {
                        ShippingStatus? pnPackageStatus = (ShippingStatus?)dpn;
                        //if package not returning warehouse and delivery directly
                        if (pnPackageStatus == ShippingStatus.Delivering && planNoteModels.All(p => p.PackageStatus != (int?)ShippingStatus.ReturningWarehouse))
                        {
                            //change delivering to returning warehouse for counting
                            if (planNoteModels.Any(p => p.PackageStatus == (int?)ShippingStatus.ReadyForPickUp))
                            {
                                pnPackageStatus = ShippingStatus.ReturningWarehouse;
                            }
                        }

                        var pn = planNoteModels.Where(p => p.PackageStatus == dpn).FirstOrDefault();
                        var pkgLocModel = new PackageLocationModel()
                        {
                            // below info follow ones from plan note.
                            PlanId = pn.PlanId,
                            ShippingStatus = pnPackageStatus.Value,
                            ShippingStatusStr = pnPackageStatus.Value.GetLocalizedEnum(_localizationService, _workContext),

                            // below info are not really useful for our purpose
                            // Thus, should NOT be used for this purpose!
                            Rank = null,
                            LocationStatus = pn.LocationId.HasValue ? _logisticsService.GetLogisticsLocationById(pn.LocationId.Value).Status : LocationStatus.Unknown,
                        };
                        pkgLocModelList.Add(pkgLocModel);
                    }
                    tempList.AddRange(pkgLocModelList);
                } // foreach packagesList 

                model.PackageLocations = model.PackageLocations.Concat(tempList).ToList();
            }
            catch (Exception exc)
            {
                ErrorNotification(exc, false);
            }

            return model;
        }

        [NonAction]
        private IList<PackageLocationModel> PreparePackageLocationModelList(ICollection<PlanNote> planNotes)
        {
            if (planNotes == null)
                throw new ArgumentNullException("planNotes");

            var pkgLocModelList = new List<PackageLocationModel>(planNotes.Count());

            foreach (var pn in planNotes)
            {
                var location = _logisticsService.GetLogisticsLocationById(pn.LocationId.Value);

                PlanNoteStatus pnAction;
                PlanStatus pnPlanStatus;
                int pnCustomerId = -1;
                int? pnPackageId = null;
                ShippingStatus? pnPackageStatus = null;
                int? addressId = null;

                pn.ExtractPlanNoteStringContent(out pnAction, out pnPlanStatus, out pnCustomerId,
                                                out pnPackageId, out pnPackageStatus, out addressId);

                Address addr = null;
                if (addressId.HasValue)
                {
                    addr = _addressService.GetAddressById(addressId.Value);
                }

                var pkgLocModel = new PackageLocationModel()
                {
                    LocationId = location.Id,
                    AddressId = addr != null ? addr.Id : location.AddressId,
                    Address = addr != null ? addr.ToModel() : location.Address.ToModel(),
                    Action = location.Id == location.Package.PickUpLocationId ? "Pick Up" : "Delivery",
                    PackageId = location.PackageId.Value,

                    // below info follow ones from plan note.
                    PlanId = pn.PlanId,
                    CourierId = pnCustomerId,
                    ShippingStatus = pnPackageStatus.Value,
                    ShippingStatusStr = pnPackageStatus.Value.GetLocalizedEnum(_localizationService, _workContext),
                    UpdatedOnUtc = pn.CreatedOnUtc,
                    UpdatedOn = _dateTimeHelper.ConvertToUserTime(pn.CreatedOnUtc, DateTimeKind.Utc),

                    // below info are not really useful for our purpose
                    // Thus, should NOT be used for this purpose!
                    Rank = null,
                    LocationStatus = location.Status,
                };

                if (addr != null)
                {
                    pkgLocModel.AddressZone = _logisticsService.GetZoneByZipPostalCode(addr.ZipPostalCode,
                                                                                       addr.Country.TwoLetterIsoCode).Name;
                }
                else
                {
                    pkgLocModel.AddressZone = _logisticsService.GetZoneByZipPostalCode(location.Address.ZipPostalCode,
                                                                                       location.Address.Country.TwoLetterIsoCode).Name;
                }

                pkgLocModel.UpdatedOnStr = pkgLocModel.UpdatedOn.Value.ToString("g");

                pkgLocModelList.Add(pkgLocModel);
            } // foreach planNotes 

            return pkgLocModelList;
        }

        [NonAction]
        protected LogisticsPackageModel PreparePkgsForList(LogisticsPackage package)
        {
            try
            {
                var createdTime = _dateTimeHelper.ConvertToUserTime(package.CreatedOnUtc, System.DateTimeKind.Utc);
                var pkgPickUpLoc = package.PickUpLocation;
                var pkgDeliveryLoc = package.DeliveryLocation;
                var pickUpZone = _logisticsService.GetZoneByZipPostalCode(pkgPickUpLoc.Address.ZipPostalCode,
                                                                                pkgPickUpLoc.Address.Country.TwoLetterIsoCode);

                var PkgModel = new LogisticsPackageModel()
                {
                    Id = package.Id,
                    PickUpDistrict = pkgPickUpLoc.District == null ? pkgDeliveryLoc.District.Name : pkgPickUpLoc.District.Name,
                    DeliveryDistrict = pkgDeliveryLoc.District.Name,
                    PickUpZone = pickUpZone == null ? "" : pickUpZone.Name,
                    DeliveryZone = _logisticsService.GetZoneByZipPostalCode(pkgDeliveryLoc.Address.ZipPostalCode,
                                                                                pkgDeliveryLoc.Address.Country.TwoLetterIsoCode).Name,
                    StatusName = package.Status.GetLocalizedEnum(_localizationService, _workContext),
                    ShipmentId = package.ShipmentId,
                    OrderId = package.Shipment.OrderId,
                    TrackingNumber = package.Shipment == null ? "N/A" : package.Shipment.TrackingNumber,
                    CreatedOn = createdTime,
                    CreatedOnStr = createdTime.ToString("g"),

                    OrderKindId = package.OrderKindId
                };
                return PkgModel;
            }
            catch (Exception)
            {
                throw new NopException("The package #{0} (pick#{1}, deliver#{2})is corrupted.", package.Id, package.PickUpLocationId, package.DeliveryLocationId);
            }
        }


        # endregion

        #region Addresses

        public ActionResult AddressEdit(int locationId, bool? edit)
        {
            //TODO: refine address change function.
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var location = _logisticsService.GetLogisticsLocationById(locationId);
            if (location == null)
                throw new ArgumentException("No address found with the specified id", "locationId");

            var address = _addressService.GetAddressById(location.AddressId);
            if (address == null)
                throw new ArgumentException("No address found with the specified id", "addressId");

            var customer = _customerService.GetCustomerById(location.Package.ApplicatorId.Value);
            if (customer == null)
            {
                throw new ArgumentException("The customer associated with the location is wrong.");
            }

            var model = new LogisticsLocationModel();

            var addressList =
                customer.Addresses.Select(a => new SelectListItem() { Selected = false, Text = a.FormattedAddress(), Value = a.Id.ToString() })
                .ToList();
            addressList.Insert(0, new SelectListItem() { Selected = true, Text = "Add a new address", Value = "0" });
            model.CustomProperties.Add("addresslist", addressList);
            model.CustomProperties.Add("packageid", location.PackageId);

            bool editExisting = edit.GetValueOrDefault(false);
            model.CustomProperties.Add("editexist", editExisting);

            model.Id = locationId;
            model.AddressId = editExisting ? address.Id : 0;
            model.Address = address.ToModel();
            model.Address.FirstNameEnabled = true;
            model.Address.FirstNameRequired = true;
            model.Address.LastNameEnabled = true;
            model.Address.LastNameRequired = true;
            model.Address.EmailEnabled = true;
            model.Address.EmailRequired = false;
            model.Address.CompanyEnabled = _addressSettings.CompanyEnabled;
            model.Address.CompanyRequired = _addressSettings.CompanyRequired;
            model.Address.CountryEnabled = _addressSettings.CountryEnabled;
            model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled;
            model.Address.CityEnabled = _addressSettings.CityEnabled;
            model.Address.CityRequired = _addressSettings.CityRequired;
            model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled;
            model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired;
            model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled;
            model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required;
            model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled;
            model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired;
            model.Address.CellPhoneEnabled = _addressSettings.CellPhoneEnabled;
            model.Address.CellPhoneRequired = _addressSettings.CellPhoneRequired;
            model.Address.PhoneEnabled = _addressSettings.PhoneEnabled;
            model.Address.PhoneRequired = _addressSettings.PhoneRequired;
            model.Address.FaxEnabled = _addressSettings.FaxEnabled;
            model.Address.FaxRequired = _addressSettings.FaxRequired;

            //countries
            model.Address.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" });
            foreach (var c in _countryService.GetAllCountries(true))
                model.Address.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == address.CountryId) });
            //states
            var states = address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, true).ToList() : new List<StateProvince>();
            if (states.Count > 0)
            {
                foreach (var s in states)
                    model.Address.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) });
            }
            else
                model.Address.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" });

            return View(model);
        }

        [HttpPost]
        [ValidateInput(false)]
        public ActionResult AddressEdit(LogisticsLocationModel model, bool editExisting)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var location = _logisticsService.GetLogisticsLocationById(model.Id);
            if (location == null)
            {
                ErrorNotification("No address found with the specified location id");
                return RedirectToAction("Index");
            }

            var customer = _customerService.GetCustomerById(location.Package.ApplicatorId.Value);
            if (customer == null)
            {
                ErrorNotification("The customer associated with the location is wrong.");
                return RedirectToAction("Index");
            }

            var locationType = String.Empty;
            if (location.Id == location.Package.PickUpLocation.Id)
            {
                locationType = "Pickup";
            }
            else if (location.Id == location.Package.DeliveryLocation.Id)
            {
                locationType = "Delivery";
            }

            try
            {
                Address address = null;
                if (editExisting)
                {
                    address = _addressService.GetAddressById(model.Address.Id);
                    if (address == null)
                        throw new ArgumentException("No address found with the specified id");

                    address = model.Address.ToEntity(address);
                    _addressService.UpdateAddress(address);

                    var pkgNoteContent = String.Format("{0}|{1}|{2}", "Address Update", locationType, "Existing");
                    _logisticsService.CreatePackageNote(location.Package.Id,
                                                        PackageNoteType.SystemEvent,
                                                        pkgNoteContent,
                                                        _workContext.CurrentCustomer.Id,
                                                        false,
                                                        null,
                                                        address);

                    SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Addresses.Updated"));
                    return RedirectToAction("AddressEdit", new { locationId = model.Id, edit = true });
                }
                else
                {
                    if (model.AddressId != 0)
                    {
                        address = customer.Addresses.SingleOrDefault(a => a.Id == model.AddressId);
                        if (address == null)
                        {
                            throw new NopException("Selected address cannot be found.");
                        }
                        location.Address = address;
                        _logisticsService.UpdateLogisticsLocation(location);

                        var pkgNoteContent = String.Format("{0}|{1}", "Address Update", locationType);
                        _logisticsService.CreatePackageNote(location.Package.Id,
                                                            PackageNoteType.SystemEvent,
                                                            pkgNoteContent,
                                                            _workContext.CurrentCustomer.Id,
                                                            false,
                                                            null,
                                                            address);

                        return RedirectToAction("PackageEdit", new { id = location.PackageId });
                    }
                    else
                    {
                        var res = new Nop.Admin.Validators.Common.AddressValidator(_localizationService, _logisticsService).Validate(model.Address);

                        if (res.IsValid)
                        {
                            address = customer.Addresses.ToList().FindAddress(
                                model.Address.FirstName, model.Address.LastName,
                                model.Address.PhoneNumber,
                                model.Address.CellPhoneNumber,
                                model.Address.Email,
                                model.Address.FaxNumber,
                                model.Address.Company,
                                model.Address.Address1, model.Address.Address2,
                                model.Address.City,
                                model.Address.StateProvinceId, model.Address.ZipPostalCode,
                                model.Address.CountryId);
                            if (address == null)
                            {
                                address = model.Address.ToEntity();
                                address.Id = 0;
                                address.CreatedOnUtc = DateTime.UtcNow;
                            }

                            customer.Addresses.Add(address);
                            _customerService.UpdateCustomer(customer);

                            location.Address = address;
                            _logisticsService.UpdateLogisticsLocation(location);

                            var pkgNoteContent = String.Format("{0}|{1}", "Address Update", locationType);
                            _logisticsService.CreatePackageNote(location.Package.Id,
                                                                PackageNoteType.SystemEvent,
                                                                pkgNoteContent,
                                                                _workContext.CurrentCustomer.Id,
                                                                false,
                                                                null,
                                                                location.Address);

                            return RedirectToAction("PackageEdit", new { id = location.PackageId });
                        }
                    }
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PackageEdit", new { id = location.PackageId });
            }

            var addressList =
                customer.Addresses.Select(a => new SelectListItem() { Selected = false, Text = a.FormattedAddress(), Value = a.Id.ToString() })
                .ToList();
            addressList.Insert(0, new SelectListItem() { Selected = true, Text = "Add a new address", Value = "0" });
            model.CustomProperties.Add("addresslist", addressList);
            model.CustomProperties.Add("packageid", location.PackageId);
            model.CustomProperties.Add("editexist", editExisting);

            //If we got this far, something failed, redisplay form
            model.Address.FirstNameEnabled = true;
            model.Address.FirstNameRequired = true;
            model.Address.LastNameEnabled = true;
            model.Address.LastNameRequired = true;
            model.Address.EmailEnabled = true;
            model.Address.EmailRequired = true;
            model.Address.CompanyEnabled = _addressSettings.CompanyEnabled;
            model.Address.CompanyRequired = _addressSettings.CompanyRequired;
            model.Address.CountryEnabled = _addressSettings.CountryEnabled;
            model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled;
            model.Address.CityEnabled = _addressSettings.CityEnabled;
            model.Address.CityRequired = _addressSettings.CityRequired;
            model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled;
            model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired;
            model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled;
            model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required;
            model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled;
            model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired;
            model.Address.CellPhoneEnabled = _addressSettings.CellPhoneEnabled;
            model.Address.CellPhoneRequired = _addressSettings.CellPhoneRequired;
            model.Address.PhoneEnabled = _addressSettings.PhoneEnabled;
            model.Address.PhoneRequired = _addressSettings.PhoneRequired;
            model.Address.FaxEnabled = _addressSettings.FaxEnabled;
            model.Address.FaxRequired = _addressSettings.FaxRequired;

            //countries
            model.Address.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" });
            foreach (var c in _countryService.GetAllCountries(true))
                model.Address.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) });
            //states
            var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, true).ToList() : new List<StateProvince>();
            if (states.Count > 0)
            {
                foreach (var s in states)
                    model.Address.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) });
            }
            else
                model.Address.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" });

            return View(model);
        }

        public ActionResult TypeInconclusiveAddresses()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            try
            {
                ViewBag.DistrictName = _localizationService.GetResource("Admin.Logistics.Common.Fields.AllDistricts");

                if (district != null)
                {
                    ViewBag.DistrictName = district.Name;
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc, false);
                return AccessDeniedView();
            }

            return View();
        }

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult TypeInconclusiveAddressList(GridCommand command)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            var typeInconclusiveAddrs = _logisticsService.GetAllTypeInconclusiveAddresses(districtId, command.Page - 1, command.PageSize);

            StringBuilder sb = new StringBuilder();
            foreach (var type in Enum.GetValues(typeof(AddressType)))
            {
                sb.Append("<button onclick='confirmType({0}, ");
                sb.Append(((int)type).ToString());
                sb.AppendFormat(")' class='t-button'>{0}</button>", ((AddressType)type).GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id));
            }
            var typeOptionsHtmlTemplate = sb.ToString();

            var gridModel = new GridModel<AddressModel>
            {
                Data = typeInconclusiveAddrs.Select(x =>
                {
                    var model = x.ToModel();
                    model.CustomProperties["CompanyOrName"] = x.GetCompanyNameOrFullName();
                    model.CustomProperties["VerifiedType"] = x.VerifiedType.ToString();
                    model.CustomProperties["VerifiedTypeId"] = x.VerifiedTypeId;
                    model.CustomProperties["TypeOptionHtml"] = String.Format(typeOptionsHtmlTemplate, x.Id);
                    model.CustomProperties["EncodedAddress"] = Url.Encode(model.FormattedAddress); // field for map query
                    return model;
                }),
                Total = typeInconclusiveAddrs.TotalCount
            };
            return new JsonResult
            {
                Data = gridModel
            };
        }

        [HttpPost]
        public ActionResult ConfirmAddressType(int addressId, int confirmedTypeId)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var addr = _addressService.GetAddressById(addressId);
            if (addr == null)
                return Json(new { Result = false, Message = "addressId is invalid." });

            if (confirmedTypeId == (int)AddressType.Unknown)
            {
                addr.TypeId = null;
                addr.VerifiedTypeId = null;
            }
            else
            {
                addr.TypeId = confirmedTypeId;
                addr.VerifiedTypeId = confirmedTypeId;
            }

            _addressService.UpdateAddress(addr);
            return Json(new { Result = true });
        }
        #endregion

        #region Package list

        public ActionResult PackageList()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            Region region = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            var model = new PackageListModel();
            try
            {
                model.DistrictId = districtId.Value;

                model.DistrictName = _localizationService.GetResource("Admin.Logistics.Common.Fields.AllDistricts");

                var districtlist = _logisticsService.GetAllDistricts();
                var zoneList = _logisticsService.GetAllZones("CA");

                //Only administrator can see all districts
                if (district != null)
                {
                    model.DistrictName = district.Name;
                    region = district.Region;
                    districtlist = _logisticsService.GetAllDistrictsInRegion(region);
                    zoneList = zoneList.Where(x => x.DistrictId == districtId).ToList();
                }

                // TODO: Only support Canada code for now.
                model.AvailableZones = zoneList
                    .Select(x => new SelectListItem() { Value = x.Name, Text = x.Name, Selected = false })
                    .ToList();
                model.AvailableZones.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" });

                model.AvailableDistricts = districtlist
                        .Select(x => new SelectListItem() { Value = x.Id.ToString(), Text = x.Name, Selected = false })
                        .ToList();
                model.AvailableDistricts.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" });

                model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList();
                model.AvailableShippingStatuses.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" });
            }
            catch (Exception exc)
            {
                ErrorNotification(exc, false);
                return AccessDeniedView();
            }
            return View(model);
        }


        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult _PackageList(GridCommand command, PackageListModel model)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            District district = null;
            if (districtId.Value != 0)
            {
                district = _logisticsService.GetDistrictById(districtId.Value);
                if (district == null)
                    return AccessDeniedView();
            }

            DateTime? startDateValue = (model.StartDate == null) ? null
                            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone);

            DateTime? endDateValue = (model.EndDate == null) ? null
                            : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1);

            ShippingStatus[] status = model.ShippingStatus != 0 ? new ShippingStatus[] { model.ShippingStatus } : null;
            int? pickUpDistrictId = model.PickUpDistrictId > 0 ? (int?)model.PickUpDistrictId : null;
            int? deliveryDistrictId = model.DeliveryDistrictId > 0 ? (int?)model.DeliveryDistrictId : null;

            Zone pickUpZone = (Zone)_logisticsService.GetZoneByName(model.PickUpZone);
            Zone deliveryZone = (Zone)_logisticsService.GetZoneByName(model.DeliveryZone);

            //var roleList = _workContext.CurrentCustomer.CustomerRoleMappings.(i => i.CustomerId == _workContext.CurrentCustomer.Id);
            var orderKind = GetRole();
            //Load packages
            var pkgs = _logisticsService.SearchPackages(
                0,
                startDateValue,
                endDateValue,
                status,
                null,
                model.TrackingNumber,
                districtId,
                pickUpDistrictId,
                deliveryDistrictId,
                pickUpZone,
                deliveryZone,
                null,
                null,
                orderKind,
                command.Page - 1,
                command.PageSize);

            var gridModel = new GridModel<LogisticsPackageModel>
            {
                Data = pkgs.Select(PreparePkgsForList),
                Total = pkgs.TotalCount
            };
            return new JsonResult
            {
                Data = gridModel
            };
        }

        [HttpPost, ActionName("PackageList")]
        [FormValueRequired("go-to-pkg-by-Id")]
        public ActionResult GoToPkgId(PackageListModel model)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            var pkg = _logisticsService.GetPackageById(model.GoDirectlyToPkgNumber);
            if (pkg == null)
            {
                return RedirectToAction("PackageList");
            }

            if (districtId != 0 &&
                pkg.PickUpLocation.DistrictId != districtId &&
                pkg.DeliveryLocation.DistrictId != districtId)
            {
                return AccessDeniedView();
            }

            return RedirectToAction("PackageEdit", "Logistics", new { id = pkg.Id });
        }

        #endregion

        #region Package Edit

        public ActionResult PackageEdit(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            var package = _logisticsService.GetPackageById(id);
            if (package == null)
            {
                ErrorNotification(String.Format("Cannot retrieve Logistics Package with Id ({0})", id));
                return RedirectToAction("PackageList");
            }

            if (districtId != 0 &&
                package.PickUpLocation.DistrictId != districtId &&
                package.DeliveryLocation.DistrictId != districtId &&
                (!package.WarehouseId.HasValue || package.Warehouse.DistrictId != districtId) &&
                (!package.ToWarehouseId.HasValue || package.ToWarehouse.DistrictId != districtId))
            {
                return AccessDeniedView();
            }

            var model = PrepareLogisticsPackageModels2(new List<LogisticsPackage>() { package }).First();
            if (!model.DeliveryAttemptCount.HasValue)
                model.DeliveryAttemptCount = 0;
            return View(model);
        }

        [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")]
        [FormValueRequired("save", "save-continue")]
        public ActionResult PackageEdit(LogisticsPackageModel model, bool continueEditing)
        {
            if (!ModelState.IsValid)
            {
                return RedirectToAction("PackageList");
            }

            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            int? districtId = _customerService.GetDistrictId(_workContext.CurrentCustomer);
            if (districtId == null)
                return AccessDeniedView();

            var customer = _workContext.CurrentCustomer;

            // Update status of current package and its associated location
            var pkg = _logisticsService.GetPackageById(model.Id);
            var prevPkgStatus = pkg.Status;
            pkg.Status = (ShippingStatus)model.Status;

            //Update delivery count
            if (pkg.DeliveryAttemptCount != model.DeliveryAttemptCount && model.DeliveryAttemptCount >= 0)
            {
                pkg.DeliveryAttemptCount = model.DeliveryAttemptCount;
                _logisticsService.UpdatePackage(pkg);
            }

            var pkgPickUpLoc = pkg.PickUpLocation;
            var pkgDeliveryLoc = pkg.DeliveryLocation;

            // pick the latest plan (currently associated with package) to use for history purpose.
            LogisticsPlan plan = _logisticsService.PackageLatestPlan(pkg);

            // simplified way of determining location, for assigning to planNote for 
            // historical purpose. Should be accurate enough.
            var locationId = _logisticsService.PackageAssociatedLocation(pkg).Id;

            // see if package is moved from one plan to another (or out of a plan, even).
            // If SelectedPlanId == 0, this means simply remove from current plan.
            if (model.SelectedPlanId >= 0)
            {
                var pkgWasInAPlan = plan != null ? true : false;

                LogisticsPlan newPlan = null;
                if (model.SelectedPlanId > 0)
                {
                    newPlan = _logisticsService.GetPlanByPlanId(model.SelectedPlanId);
                    if (newPlan == null)
                    {
                        ErrorNotification(String.Format("Cannot retrieve Logistics Plan {0}", model.SelectedPlanId));
                        return RedirectToAction("PackageEdit", new { id = model.Id });
                    }
                }

                if (pkgWasInAPlan && _logisticsService.PackageInWarehouse(pkg) && newPlan != null)
                {
                    // check that the newPlan has the same warehouse as current plan.
                    if (plan.WarehouseId != newPlan.WarehouseId)
                    {
                        ErrorNotification("Cannot re-assign in-warehouse package between plans with different warehouses");
                        return RedirectToAction("PackageEdit", new { id = model.Id });
                    }
                }

                // user have selected somwething other than current plan.
                if (pkgWasInAPlan && plan.Id != model.SelectedPlanId)
                {
                    // package currently belongs to a plan, which is different from what the user
                    // selected. So, must remove package from current plan first!
                    _logisticsService.RemovePackagesFromLogisticsPlan(new int[] { pkg.Id }, plan.Id, customer.Id);

                    // With this package removed, check if remaining packages in the plan 
                    // has been serviced. If so, then automatically mark the plan as completed.
                    if (_logisticsService.LogisticsPlanCompleted(plan.Id))
                    {
                        _logisticsService.MarkLogisticsPlanAsCompleted(plan.Id, customer.Id);
                    }

                    plan = null;  // by here, plan must be null.
                }

                if (model.SelectedPlanId > 0 && plan == null)
                {
                    // Either the package was originally not in a plan, OR
                    // it has just been removed from its current plan.
                    // Either way, at this point, we are going to assign it to the 
                    // user-selected plan.

                    _logisticsService.AssignLogisticsPlanToPackages(new int[] { pkg.Id }, model.SelectedPlanId, customer.Id);

                    // get the newly assigned plan
                    plan = _logisticsService.GetPlanByPlanId(model.SelectedPlanId);
                }
                else if (pkgWasInAPlan && plan == null)
                {
                    SuccessNotification("Package successfully removed from plan.");
                    return RedirectToAction("PackageEdit", new { id = model.Id });
                }
            }

            // By here, if package is not associated with a plan,
            // then do not allow any package edit actions to happen.
            if (plan == null)
            {
                SuccessNotification("Partially updated the package information due to not associated with a plan", true);
                return RedirectToAction("PackageEdit", new { id = model.Id });
            }

            if (model.UpdatePickUpLocation && pkgPickUpLoc != null)
            {
                pkgPickUpLoc.Rank = (int?)model.PickupRank;
                _logisticsService.UpdateLogisticsLocation(pkgPickUpLoc);
            }

            if (model.UpdateDeliveryLocation && pkgDeliveryLoc != null)
            {
                pkgDeliveryLoc.Rank = (int?)model.DeliveryRank;
                _logisticsService.UpdateLogisticsLocation(pkgDeliveryLoc);
            }

            // Only update package if allowed and actual status change happened
            if (model.UpdatePackage && prevPkgStatus != (ShippingStatus)model.Status)
            {
                // TBD: This logic below looks weird! Can't remember why.
                //      But do not think it's harmful.
                if ((ShippingStatus)model.Status == ShippingStatus.Delivering ||
                    (ShippingStatus)model.Status == ShippingStatus.Delivered ||
                    (ShippingStatus)model.Status == ShippingStatus.ReturningWarehouseFailedDelivery ||
                    (ShippingStatus)model.Status == ShippingStatus.InWarehouseFailedDelivery)
                {
                    if (!pkgDeliveryLoc.PlanId.HasValue)
                    {
                        if (!pkgDeliveryLoc.Rank.HasValue)
                        {
                            ErrorNotification(String.Format("Delivery location order must be set to update package to the desired status: {0}", ((ShippingStatus)model.Status).GetLocalizedEnum(_localizationService, _workContext)));
                            return RedirectToAction("PackageEdit", new { id = model.Id });
                        }
                        else
                        {
                            pkgDeliveryLoc.PlanId = plan.Id;
                            _logisticsService.UpdateLogisticsLocation(pkgDeliveryLoc);
                        }
                    }
                }

                pkg.Status = (ShippingStatus)model.Status;

                // update the rest of package object's fields
                if (pkg.Status == ShippingStatus.Cancelled)
                {
                    // Currently, it is understood that Cancelled only be used for when
                    // package is cancelled AND has not been picked up at all.
                    // By setting package to automatically return to plan's warehouse, Dispatcher
                    // does not have to rank the delivery location. Also, the delivery location
                    // should disappear from driver's list (less confusing).
                    pkg.ToWarehouse = null;
                    pkg.DeliveryLocation.Rank = null;
                    pkg.DeliveryLocation.Plan = null;
                }
                else if (_logisticsService.PackageInWarehouse(pkg, checkStatusOnly: true))
                {
                    if (pkg.ToWarehouseId.HasValue)
                    {
                        pkg.Warehouse = pkg.ToWarehouse;
                        pkg.ToWarehouse = null;
                    }
                    else
                    {
                        pkg.Warehouse = plan.Warehouse;
                    }
                }

                else if (_logisticsService.PackageInTransientState(pkg))
                {
                    // thus, cannot be in warehouse.
                    pkg.WarehouseId = null;
                }


                // Update shipment status
                if (pkg.Shipment != null && pkg.Shipment.Order != null)
                {
                    pkg.Shipment.Order.ShippingStatus = pkg.Status;
                }

                _logisticsService.UpdateLogisticsPackage(pkg, PlanNoteStatus.UpdatePackage, customer.Id, true, model.StatusUpdateValidForCommission, createLog: true);

                //send email
                if (pkg.Status == ShippingStatus.Delivered)
                {
                    _workflowMessageService.SendShipmentDeliveredCustomerNotification(pkg.Shipment, 1);
                }

                if (pkg.Status == ShippingStatus.FailedPickUp)
                {
                    _workflowMessageService.SendShipmentFailedPickupOriginNotification(pkg, _dateTimeHelper.ConvertToUserTime(pkg.Shipment.CreatedOnUtc), 1);
                }
            }

            // should re-direct back to packageEdit for this package (user can see the newest state!)
            SuccessNotification("Update Save successful!", true);
            return continueEditing ? RedirectToAction("PackageEdit", pkg.Id) : RedirectToAction("PackageList");
        }
        #endregion

        #region package notes
        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult PackageNotesSelect(int packageId, GridCommand command)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var pkgNotes = _logisticsService.GetPackageNotesByPackageId(packageId);
            if (pkgNotes == null)
                throw new ArgumentException(_localizationService.GetResource("Admin.Logistics.PackageNote.NotFoundWithId"));

            //package notes
            IList<PackageNoteModel> packageNoteModels = new LogisticsPackageModel().PackageNotes;
            foreach (var pkn in pkgNotes
                .OrderByDescending(pkn => pkn.CreatedOnUtc))
            {
                var pknModel = pkn.ToModel();

                pknModel.TypeString = pknModel.Type.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id);
                pknModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(pkn.CreatedOnUtc, DateTimeKind.Utc).ToString("G");
                pknModel.ShippingStatus = pknModel.ShippingStatusId.HasValue ? ((ShippingStatus)pknModel.ShippingStatusId.Value).GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id) : "N/A";

                if (pkn.AddressId.HasValue)
                {
                    var address = _addressService.GetAddressById(pkn.AddressId.Value);
                    pknModel.Address = address.ToModel();
                    pknModel.AddresId = pknModel.Address.Id;
                    pknModel.AddressString = address.FormattedAddress();
                }
                else
                {
                    pknModel.AddressString = "N/A";
                }

                packageNoteModels.Add(pknModel);
            };

            var packageNoteLists = new PagedList<PackageNoteModel>(packageNoteModels, command.Page - 1, command.PageSize);
            var model = new GridModel<PackageNoteModel>
            {
                Data = packageNoteLists,
                Total = packageNoteLists.TotalCount
            };
            return new JsonResult
            {
                Data = model
            };
        }

        [ValidateInput(false)]
        public ActionResult PackageNoteAdd(int packageId, bool displayToCustomer, string message)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var package = _logisticsService.GetPackageById(packageId);
            if (package == null || String.IsNullOrWhiteSpace(message))
            {
                return Json(new { Result = false });
            }

            var customer = _workContext.CurrentCustomer;

            _logisticsService.CreatePackageNote(packageId, PackageNoteType.AdminNote, message, customer.Id, displayToCustomer, null, null);

            SuccessNotification(_localizationService.GetResource("Admin.Logistics.PackageNote.Added.Successfully"), true);
            return Json(new { Result = true });
        }

        [GridAction(EnableCustomBinding = true)]
        public ActionResult PackageNotesUpdate(int packageId, int packageNoteId, PackageNoteModel model, GridCommand command)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content(_localizationService.GetResource("Admin.Accessdenied.Title"));

            var package = _logisticsService.GetPackageById(packageId);
            if (package == null)
                return Content(_localizationService.GetResource("Admin.Logistics.Package.NotFound"));

            var packageNote = package.PackageNotes.SingleOrDefault(pkn => pkn.Id == packageNoteId);
            if (packageNote == null)
                return Content(_localizationService.GetResource("Admin.Logistics.PackageNote.NotFound"));

            packageNote.DisplayToCustomer = model.DisplayToCustomer;
            _logisticsService.UpdatePackageNote(packageNote);

            SuccessNotification(_localizationService.GetResource("Admin.Logistics.PackageNote.Update"), true);
            return PackageNotesSelect(packageId, command);
        }

        #endregion

        #region plan note
        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult PlanNotesSelect(int planId, GridCommand command)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            var pnNotes = _logisticsService.GetPlanNotesByPlanId(planId);
            if (pnNotes == null)
                throw new ArgumentException(_localizationService.GetResource("Admin.Logistics.PlanNote.NotFoundWithId"));

            //plan notes
            IList<PlanNoteModel> planNoteModels = new LogisticsPackageModel().PlanNotes;
            foreach (var pn in pnNotes
                .OrderByDescending(pn => pn.CreatedOnUtc))
            {
                var pnModel = pn.ToModel();

                pnModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(pn.CreatedOnUtc, DateTimeKind.Utc).ToString("G");

                planNoteModels.Add(pnModel);
            };

            var planNoteLists = new PagedList<PlanNoteModel>(planNoteModels, command.Page - 1, command.PageSize);
            var model = new GridModel<PlanNoteModel>
            {
                Data = planNoteLists,
                Total = planNoteLists.TotalCount
            };

            return new JsonResult
            {
                Data = model
            };
        }

        [GridAction(EnableCustomBinding = true)]
        public ActionResult PlanNotesUpdate(int planId, int planNoteId, PlanNoteModel model, GridCommand command)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content(_localizationService.GetResource("Admin.AccessDenied.Title"));

            var plan = _logisticsService.GetPlanByPlanId(planId);
            if (plan == null)
                return Content(_localizationService.GetResource("Admin.Logistics.Plan.NotFound"));

            var planNote = plan.PlanNotes.SingleOrDefault(pkn => pkn.Id == planNoteId);
            if (planNote == null)
                return Content(_localizationService.GetResource("Admin.Logistics.PlanNote.NotFound"));

            planNote.ValidForCommission = model.ValidForCommission;
            _logisticsService.UpdatePlanNote(planNote);

            SuccessNotification(_localizationService.GetResource("Admin.Logistics.PlanNote.Update"), true);
            return PlanNotesSelect(planId, command);
        }
        #endregion

        #region Export / Import

        public ActionResult PrintSelected(string selectedIds)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics) &&
                !_permissionService.Authorize(StandardPermissionProvider.ViewLogisticsRoutes))
                return AccessDeniedView();

            try
            {
                var packages = new List<LogisticsPackage>();
                List<string> trackingUrls = new List<string>(packages.Count);
                if (selectedIds != null)
                {
                    var ids = selectedIds
                        .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                        .Select(x => Convert.ToInt32(x))
                        .ToList();

                    foreach (var id in ids)
                    {
                        var pkg = _logisticsService.GetPackageById(id);
                        var trackingUrl = Url.RouteUrl("LogisticsTrack", new { pid = id }, Request.Url.Scheme);
                        packages.Add(pkg);
                        trackingUrls.Add(trackingUrl);
                    }
                    byte[] bytes = null;
                    using (var stream = new MemoryStream())
                    {
                        _pdfService.PrintLogisticsWaybillToPdf(stream, packages, trackingUrls, _workContext.WorkingLanguage);
                        bytes = stream.ToArray();
                    }

                    return File(bytes, "application/pdf", String.Format("pdfWaybills__Multiple_({0}_labels).pdf", ids.Count()));
                }
                else
                {
                    return RedirectToAction("PackageList");
                }
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PackageList");
            }
        }

        public ActionResult DownloadRouteListAsPdf(int planId)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics) &&
                !_permissionService.Authorize(StandardPermissionProvider.ViewLogisticsRoutes))
                return AccessDeniedView();

            try
            {
                var locations = _logisticsService.GetLogisticsLocationsByPlanId(planId);
                var trackingUrls = new List<string>(locations.Count);
                int idx = 0;
                foreach (var loc in locations)
                {
                    trackingUrls.Add(Url.RouteUrl("LogisticsTrack", new { pid = loc.Package.Id }, Request.Url.Scheme));
                    idx++;
                }

                var plannedRouteUrl = Url.RouteUrl("LogisticsRoutePlan", new { id = planId }, Request.Url.Scheme);

                byte[] bytes = null;
                using (var stream = new MemoryStream())
                {
                    _pdfService.PrintLogistcsLocationsToPdf(stream, locations, trackingUrls, plannedRouteUrl, _workContext.WorkingLanguage);
                    bytes = stream.ToArray();
                }
                var filename = String.Format("pdfRouteList__plan{0}.pdf", planId);
                Response.AppendHeader("Content-Disposition", "inline; filename=" + filename);
                return File(bytes, "application/pdf");
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PlannedRouteList", new { id = planId });
            }
        }

        public ActionResult PrintPackageWaybillAsPdf(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics) &&
                !_permissionService.Authorize(StandardPermissionProvider.ViewLogisticsRoutes))
                return AccessDeniedView();

            try
            {
                var pkg = _logisticsService.GetPackageById(id);

                var trackingUrl = Url.RouteUrl("LogisticsTrack", new { pid = id }, Request.Url.Scheme);

                byte[] bytes = null;
                using (var stream = new MemoryStream())
                {
                    _pdfService.PrintLogisticsWaybillToPdf(stream, pkg, trackingUrl, _workContext.WorkingLanguage);
                    bytes = stream.ToArray();
                }
                return File(bytes, "application/pdf", WaybillFilename.GenerateWaybillFilename(pkg.Shipment.TrackingNumber));
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PackageList");
            }
        }

        public ActionResult PrintOrderWaybillsAsPdf(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics) &&
                !_permissionService.Authorize(StandardPermissionProvider.ViewLogisticsRoutes))
                return AccessDeniedView();

            try
            {
                var order = _orderService.GetOrderById(id);
                var numShipments = order.Shipments.Count;

                // TODO: Max to be re-evaluated or to be handled in the future!
                const int maxNumShipments = 500;
                if (numShipments > maxNumShipments)
                    throw new Exception(String.Format("More than {0} shipments detected for Order with ID: {1}",
                                                      maxNumShipments, id));

                List<LogisticsPackage> packages = new List<LogisticsPackage>(numShipments);
                List<string> trackingUrls = new List<string>(numShipments);
                foreach (var shipment in order.Shipments)
                {
                    var pkg = _logisticsService.GetPackageByShipmentId(shipment.Id);
                    var trackingUrl = Url.RouteUrl("LogisticsTrack", new { pid = pkg.Id }, Request.Url.Scheme);

                    packages.Add(pkg);
                    trackingUrls.Add(trackingUrl);
                }

                byte[] bytes = null;
                using (var stream = new MemoryStream())
                {
                    _pdfService.PrintLogisticsWaybillToPdf(stream, packages, trackingUrls, _workContext.WorkingLanguage);
                    bytes = stream.ToArray();
                }
                return File(bytes, "application/pdf", String.Format("pdfWaybills__orderId_{0}.pdf", id));
            }
            catch (Exception exc)
            {
                ErrorNotification(exc);
                return RedirectToAction("PlanList");
            }
        }

        #endregion

        #region Package note template
        public ActionResult PackageNoteTemplate()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return AccessDeniedView();

            return View();
        }

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult PackageNoteTemplateList(GridCommand command)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content(_localizationService.GetResource("Admin.AccessDenied.Title"));

            var template = _logisticsService.GetPackageNoteTemplates(null, false);
            var gridModel = new GridModel<LogisticsPackageNoteTemplateModel>
            {
                Data = template.Select(x =>
                {
                    var model = new LogisticsPackageNoteTemplateModel()
                    {
                        Id = x.Id,
                        ReferenceId = x.ReferenceId,
                        SystemName = x.SystemName,
                        Content = x.Content,
                        DisplayOrder = x.DisplayOrder,
                        Published = x.Published
                    };
                    return model;
                }),
                Total = template.Count
            };
            return new JsonResult
            {
                Data = gridModel
            };
        }

        #region Create / Edit / Delete
        public ActionResult PackageNoteTemplateCreate()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content(_localizationService.GetResource("Admin.AccessDenied.Title"));

            var model = new LogisticsPackageNoteTemplateModel();
            //locales
            AddLocales(_languageService, model.Locales);
            return View(model);
        }

        [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")]
        public ActionResult PackageNoteTemplateCreate(LogisticsPackageNoteTemplateModel model, bool continueEditing)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content(_localizationService.GetResource("Admin.AccessDenied.Title"));

            if (ModelState.IsValid)
            {
                var packageNoteTemplate = new PackageNoteTemplate()
                {
                    Id = model.Id,
                    ReferenceId = model.ReferenceId,
                    SystemName = model.SystemName,
                    Content = model.Content,
                    DisplayOrder = model.DisplayOrder,
                    Published = model.Published
                };

                _logisticsService.InsertPackageNoteTemplate(packageNoteTemplate);
                //locales
                UpdateLocales(packageNoteTemplate, model);

                SuccessNotification(_localizationService.GetResource("Admin.Logistics.packageNoteTemplate.SuccessfullyAdd"));
                return continueEditing ? RedirectToAction("PackageNoteTemplateEdit", new { id = packageNoteTemplate.Id }) : RedirectToAction("packageNoteTemplate");
            }

            //If we got this far, something failed, redisplay form
            return View(model);
        }

        public ActionResult PackageNoteTemplateEdit(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageLogistics))
                return Content(_localizationService.GetResource("Admin.AccessDenied.Title"));

            var packageNoteTemplate = _logisticsService.GetPackageNoteTemplateById(id);
            if (packageNoteTemplate == null)
                //No topic found with the specified id
                return RedirectToAction("PackageNoteTemplate");

            var model = new LogisticsPackageNoteTemplateModel()
            {
                Id = packageNoteTemplate.Id,
                ReferenceId = packageNoteTemplate.ReferenceId,
                SystemName = packageNoteTemplate.SystemName,
                Content = packageNoteTemplate.Content,
                DisplayOrder = packageNoteTemplate.DisplayOrder,
                Published = packageNoteTemplate.Published
            };

            //locales
            AddLocales(_languageService, model.Locales, (locale, languageId) =>
            {
                locale.Content = packageNoteTemplate.GetLocalized(x => x.Content, languageId, false, false);
            });

            return View(model);
        }

        [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")]
        public ActionResult PackageNoteTemplateEdit(LogisticsPackageNoteTemplateModel model, bool continueEditing)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageTopics))
                return AccessDeniedView();

            var packageNoteTemplate = _logisticsService.GetPackageNoteTemplateById(model.Id);
            if (packageNoteTemplate == null)
                //No topic found with the specified id
                return RedirectToAction("packageNoteTemplate");

            if (ModelState.IsValid)
            {
                packageNoteTemplate.SystemName = model.SystemName;
                packageNoteTemplate.Content = model.Content;
                packageNoteTemplate.Published = model.Published;
                packageNoteTemplate.DisplayOrder = model.DisplayOrder;
                packageNoteTemplate.ReferenceId = model.ReferenceId;

                _logisticsService.UpdatePackageNoteTemplate(packageNoteTemplate);

                //locales
                UpdateLocales(packageNoteTemplate, model);

                SuccessNotification(_localizationService.GetResource("Admin.Logistics.packageNoteTemplate.Updated"));
                return continueEditing ? RedirectToAction("PackageNoteTemplateEdit", packageNoteTemplate.Id) : RedirectToAction("packageNoteTemplate");
            }

            //If we got this far, something failed, redisplay form
            return View(model);
        }

        [HttpPost]
        public ActionResult PackageNoteTemplateDelete(int id)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageTopics))
                return AccessDeniedView();

            var packageNoteTemplate = _logisticsService.GetPackageNoteTemplateById(id);
            if (packageNoteTemplate == null)
                //No topic found with the specified id
                return RedirectToAction("packageNoteTemplate");

            _logisticsService.DeletePackageNoteTemplate(packageNoteTemplate);

            SuccessNotification(_localizationService.GetResource("Admin.Logistics.packageNoteTemplate.Deleted"));
            return RedirectToAction("packageNoteTemplate");
        }

        #endregion

        #endregion

        private int[] GetRole()
        {
            var roleList = from r in _workContext.CurrentCustomer.CustomerRoleMappings
                           where r.CustomerId == _workContext.CurrentCustomer.Id
                           select r.CustomerRoleId;
            var getRole = _logisticsService.GetCustomerRole(roleList.ToList<int>());

            var ordeKindIdLogistic = getRole.Contains(SystemCustomerRoleNames.LogisticsManager) ? 10 : 0;
            var ordeKindIdECommerce = getRole.Contains(SystemCustomerRoleNames.eCommerceLogisticsManager) ? 20 : ordeKindIdLogistic;
            if (ordeKindIdLogistic == ordeKindIdECommerce)
                return new int[] { ordeKindIdLogistic };
            else
                return new int[] { ordeKindIdLogistic, ordeKindIdECommerce };
        }
    }
}
